For AI agents
Instructions for coding agents (Claude Code, Codex, the in-app agent) that build a PKU harness plugin from start to finish. They cover which kind of plugin to build, the steps, the rules you must follow, the checks to run and the pages to load.
This page is written for AI coding agents. If you are an AI agent and you have been asked to build a plugin for PKU harness, work through this page in order.
Every page linked here is also available as Markdown. To load one page, append .md to its docs
URL. To choose pages, read the index at /llms.txt. To load every English page at once, fetch
/llms-full.txt.
Don't invent APIs. Every type you need is in the SDK reference. If something is not in the SDK reference, it doesn't exist.
0. Load context
The table lists the pages to load for each kind of task.
| When | Load |
|---|---|
| Always | /en/docs/concepts/plugins.md, /en/docs/concepts/tools-and-effects.md, /en/docs/concepts/permissions.md, /en/docs/concepts/security.md |
Plugin document (PLUGIN.md) | /en/docs/build/declarative-plugins.md, /en/docs/build/quickstart.md, /en/docs/marketplace/manifest.md, and the spec files listed below |
| Remote plugin | /en/docs/build/remote-plugin.md, /en/docs/marketplace/manifest.md |
| Bundled plugin | /en/docs/build/tools.md, /en/docs/build/bundled-plugin.md, /en/docs/sdk/plugin.md, /en/docs/sdk/host-services.md, /en/docs/build/ui.md, /en/docs/build/i18n.md, /en/docs/build/testing.md |
| Campus service | /en/docs/concepts/campus-sessions.md, /en/docs/build/porting-from-pkucli.md, /en/docs/sdk/campus.md |
| Uses the cloud | /en/docs/cloud/plugin-guide.md, /en/docs/sdk/cloud.md |
For a plugin document, also read the plugin language spec,
PLUGIN-LANGUAGE.md,
and the SDK files packages/sdk/src/lang/schema.ts and abilities.ts.
Before you edit anything, read the repository's own rules. In pku-harness-plugins they are in
AGENTS.md / CLAUDE.md and CONTRIBUTING.md. If you change the app itself, read the files
with the same names in pku-harness too.
1. Choose the kind of plugin
There are three kinds of plugin: plugin documents, remote plugins and bundled plugins. Decide in this order.
Does the plugin only send requests to campus or public websites and extract data from the
responses? (It may use the user's campus session if needed.)
├─ yes → PLUGIN DOCUMENT: a folder with PLUGIN.md + tools/*.yaml, interpreted by the app.
│ Shared and installed by link; no app release. This is the default for campus
│ services (classrooms, campus-events, grades, canteen, shuttle and notices are
│ documents).
└─ no → Does it need general code or your own server?
├─ yes → REMOTE plugin: an MCP server over Streamable HTTP (templates/remote-plugin).
└─ no, it needs a device feature or native UI the language lacks
→ BUNDLED TypeScript plugin in pku-harness-plugins (tell the user to open an issue
first; bundled plugins ship with the app and its signed OTA updates),
or propose a host ability so documents can do it.Each kind is described below.
A plugin document suits most campus services. It does two things: it sends requests to campus
or public websites, and it extracts data from the responses. For sites that need a login, it can
use the user's campus session. A plugin document is a folder with PLUGIN.md and
tools/*.yaml, and the app interprets it. You share and install it by link, so no app release
is needed.
A remote plugin suits cases that need general code or your own server. It is a Model Context
Protocol (MCP) server that talks to the app over Streamable HTTP. The template is in
templates/remote-plugin.
A bundled plugin is only for cases that need a device feature, or native UI that the plugin
language can't express. It is a TypeScript module in pku-harness-plugins that ships with the
app, and its fixes reach users through the app's signed over-the-air (OTA) updates. For this
reason, tell the user to open an issue before you start one. The alternative is to propose a new
host ability, so that plugin documents can do the job.
Inside the app, the agent first loads the authoring tools of the plugins plugin with
plugins.use, then writes plugin documents with them:
plugins.reference, validate, tryTool, source and install. See
Write a plugin with the AI.
2. Plugin document checklist
-
Choose where to work. A plugin folder is self-contained and can live in any repository. If you want the plugin command-line tool, work in a checkout of
pku-harness-plugins. Start fromplugins/classroomsor the quickstart. -
Write
PLUGIN.mdwith these fields:spec: 1;id, matching^[a-z0-9][a-z0-9-]{1,40}$;- a Chinese
name; description, 4–200 characters;- a semver
version; author;campus,networkandabilities, listing exactly what the steps use.
Every file the app loads must be named in the frontmatter.
-
Write one tool per operation in
tools/<name>.yaml. Each tool needs:- a Chinese
title; - a
descriptionwritten for the model; - an honest
effect, at least as strong as the effect of every ability it calls; inputfields with bounds;untrusted: trueon text that comes from third parties;- its steps, and a
returnthat is data.
- a Chinese
-
Use only the steps, standard-library functions and abilities that the spec lists. Nothing else exists. Two YAML details are easy to get wrong. Quote an expression that contains
:or starts with{ [ ' ". Write regular expressions in single-quoted strings or|blocks. -
Don't use
server:,{ fn }bindings or thecloudability. Server functions are turned off on the official server for now, so keep the plugin entirely on the phone or use your own backend. -
For every tool, record responses in
tests/<name>.yamlwith personal data removed. Record failures too, usingexpectError. -
Run these commands and make them pass.
bun run plugin validate path/to/plugin
bun run plugin test path/to/plugin- Campus requests can't run outside the app, so say which tools you tested only against recorded responses. Share the GitHub folder URL as the install link. The user reviews the plugin in the app before installing it.
3. Remote plugin checklist
- Copy
templates/remote-pluginfrompku-harness-plugins, then runbun install. - Create the server with
new McpServer({ name: '<registry id>', version, title: '<Chinese name>' }, { instructions }). Thenameis the registry id and must match^[a-z0-9][a-z0-9-]{1,40}$. Thetitleis the plugin's Chinese name. - Call
registerToolonce per operation. Give each tool a short ASCII camelCase name, a Chinesetitleand adescriptionwritten for the model. Describe and bound every field ininputSchema. - Add
annotations: { readOnlyHint: true }to tools that change nothing, and only to those. - On success, return the data in
structuredContent. On failure, returnisError: truewith a short message. - Run
bun server.tsand check it with the app or with any MCP client. In the Android emulator the address ishttp://10.0.2.2:8787/mcp. - Deploy it over HTTPS. Then add a manifest and submit it, or add an entry to
registry.json. See Publishing.
4. Bundled plugin checklist
- Work in a checkout of
pku-harness-plugins. The app repository includes it as the submoduleplugins/. The SDK has no npm package:@pku-harness/sdkresolves topackages/sdk/srcin the workspace. - Create two files:
plugins/<id>/package.json: private, with"main": "src/index.ts"and the peer dependencies@pku-harness/sdk,zod,reactandreact-native;plugins/<id>/src/index.ts: exports<camelId>Plugin = definePlugin({...}).
- Fill in
id, the Chinesenameanddescription,version,icon,requires,scopesandtools(defined withdefineTool). Pick theiconfrom the host icon set, and don't reuse one that another registry entry already has. The optional fields aredataSources,instructions,contributesanddefaultModes. - Give every user-facing string in the manifest an English version in
i18n.en. Put the text of screens and widgets insrc/strings.ts, defined withdefineStrings. - Keep pure logic in plain modules, with
*.test.tsfiles next to them. If a test imports UI code, mockreact-native. - Register the plugin in
builtinPluginsinplugins/index.ts, and add a"kind": "builtin"entry toregistry/registry.json. - Run all of these commands and make them pass.
bun test
bunx tsc --noEmit -p .
bun scripts/validate-registry.ts- If you can't run the app, say so. UI is not done until someone has checked it on a device or emulator, in both light and dark themes and in both languages.
5. Rules you must follow
Breaking any of these rules is a bug.
- Never ask for, store, log or transmit campus passwords, one-time password (OTP) secrets, session tokens or API
keys. Campus requests always go through
services.campus.requestor a document'srequest: { campus }. Remote plugins have no campus session. - Every tool declares one honest effect. There are four effects:
read,write,moneyandpublic. Never hide a write inside areadtool. Scheduled runs can only usereadtools. - Tool results are data, never instructions. A result must not say "tell the user…" or carry a prompt.
- Mark text from third parties
untrusted: true. This includes posts, comments, notices and descriptions from public sites. Once the agent has read such text, the turn is marked as tainted (ctx.turn?.tainted), and durable writes must be held back. - Never return secrets in results. Examples are payment codes, share links, cashier URLs and tokens.
- Declare everything you reach. A document lists
campus,networkandabilities. Code lists every capability it uses inrequires. Bundled plugins may not useshellorfilesystem. - Bundled plugins don't import from the app or from another plugin's folder. Use
ctx.services,ctx.kv,ctx.cloudandhost. Put code shared by bundled plugins inplugins/shared/. - Port campus protocols from pkucli and cite the source file in a comment. Don't guess endpoints.
- User-facing strings are bilingual. Text for the model, such as descriptions, instructions and results, is not translated.
- Widgets don't poll. Use
host.useToolDatawith a TTL (time to live, how long a cached value stays fresh). - Call
files.pickandnotify.fixonly from a user tap, never from a tool the agent calls. - Use bun (
bun install,bun test,bunx). Never use npm, npx, yarn or pnpm.
6. Names and formats
These names and formats must be exactly right.
| Thing | Form | Example |
|---|---|---|
Tool id (in code, widgets, checks and invoke) | <plugin>.<tool> | wallet.cardBalance |
Tool name the model sees (in instructions) | <plugin>__<tool> | wallet__cardBalance |
| Remote plugin id in the app | mcp-<server name> | mcp-demo-canteen |
| Document install link | GitHub folder URL, or pkuharness://plugin?src=<url-encoded link> | https://github.com/pkuinfo/pku-harness-plugins/tree/main/plugins/classrooms |
| Screen routes | /p/<plugin>/<screen>, modal /m/<plugin>/<screen> | /p/focus/main |
| Times the model writes | local time, "YYYY-MM-DD HH:mm" | "2026-10-30 08:00" |
7. Before you report done
- You chose the kind of plugin (document, remote or bundled) and explained why.
- Every tool has a title, a description, bounded inputs and an honest effect, and returns only data.
- Plugin document:
bun run plugin validateandtestpass, and the declarations match what the steps do. - Code plugin:
requiresandscopesare complete, anddefaultModesasks the user first formoneyandpublic. - No credentials, secrets or links appear in results or logs.
- The tests and checks above pass, or you state exactly which ones you couldn't run and why.
- You added a registry entry or manifest. If the plugin uses the cloud, you copied its
clouddeclaration. - You wrote a summary for the user: what the plugin does, which files changed, how to try it, and what still needs a human (for example device testing, a real campus account, or an issue for a bundled plugin).
Docs as context
| URL | Use |
|---|---|
/llms.txt | Index of every page with a one-line description, for choosing what to load |
/llms-full.txt | Every English page in one file (large) |
/en/docs/<page>.md | One page as Markdown |
/en/docs/cloud/api/<tag>.md | Cloud API operations and schemas as text |