PKU harness

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.

WhenLoad
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

  1. 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 from plugins/classrooms or the quickstart.

  2. Write PLUGIN.md with 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, network and abilities, listing exactly what the steps use.

    Every file the app loads must be named in the frontmatter.

  3. Write one tool per operation in tools/<name>.yaml. Each tool needs:

    • a Chinese title;
    • a description written for the model;
    • an honest effect, at least as strong as the effect of every ability it calls;
    • input fields with bounds;
    • untrusted: true on text that comes from third parties;
    • its steps, and a return that is data.
  4. 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.

  5. Don't use server:, { fn } bindings or the cloud ability. Server functions are turned off on the official server for now, so keep the plugin entirely on the phone or use your own backend.

  6. For every tool, record responses in tests/<name>.yaml with personal data removed. Record failures too, using expectError.

  7. Run these commands and make them pass.

bun run plugin validate path/to/plugin
bun run plugin test path/to/plugin
  1. 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

  1. Copy templates/remote-plugin from pku-harness-plugins, then run bun install.
  2. Create the server with new McpServer({ name: '<registry id>', version, title: '<Chinese name>' }, { instructions }). The name is the registry id and must match ^[a-z0-9][a-z0-9-]{1,40}$. The title is the plugin's Chinese name.
  3. Call registerTool once per operation. Give each tool a short ASCII camelCase name, a Chinese title and a description written for the model. Describe and bound every field in inputSchema.
  4. Add annotations: { readOnlyHint: true } to tools that change nothing, and only to those.
  5. On success, return the data in structuredContent. On failure, return isError: true with a short message.
  6. Run bun server.ts and check it with the app or with any MCP client. In the Android emulator the address is http://10.0.2.2:8787/mcp.
  7. Deploy it over HTTPS. Then add a manifest and submit it, or add an entry to registry.json. See Publishing.

4. Bundled plugin checklist

  1. Work in a checkout of pku-harness-plugins. The app repository includes it as the submodule plugins/. The SDK has no npm package: @pku-harness/sdk resolves to packages/sdk/src in the workspace.
  2. Create two files:
    • plugins/<id>/package.json: private, with "main": "src/index.ts" and the peer dependencies @pku-harness/sdk, zod, react and react-native;
    • plugins/<id>/src/index.ts: exports <camelId>Plugin = definePlugin({...}).
  3. Fill in id, the Chinese name and description, version, icon, requires, scopes and tools (defined with defineTool). Pick the icon from the host icon set, and don't reuse one that another registry entry already has. The optional fields are dataSources, instructions, contributes and defaultModes.
  4. Give every user-facing string in the manifest an English version in i18n.en. Put the text of screens and widgets in src/strings.ts, defined with defineStrings.
  5. Keep pure logic in plain modules, with *.test.ts files next to them. If a test imports UI code, mock react-native.
  6. Register the plugin in builtinPlugins in plugins/index.ts, and add a "kind": "builtin" entry to registry/registry.json.
  7. Run all of these commands and make them pass.
bun test
bunx tsc --noEmit -p .
bun scripts/validate-registry.ts
  1. 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.

  1. 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.request or a document's request: { campus }. Remote plugins have no campus session.
  2. Every tool declares one honest effect. There are four effects: read, write, money and public. Never hide a write inside a read tool. Scheduled runs can only use read tools.
  3. Tool results are data, never instructions. A result must not say "tell the user…" or carry a prompt.
  4. 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.
  5. Never return secrets in results. Examples are payment codes, share links, cashier URLs and tokens.
  6. Declare everything you reach. A document lists campus, network and abilities. Code lists every capability it uses in requires. Bundled plugins may not use shell or filesystem.
  7. Bundled plugins don't import from the app or from another plugin's folder. Use ctx.services, ctx.kv, ctx.cloud and host. Put code shared by bundled plugins in plugins/shared/.
  8. Port campus protocols from pkucli and cite the source file in a comment. Don't guess endpoints.
  9. User-facing strings are bilingual. Text for the model, such as descriptions, instructions and results, is not translated.
  10. Widgets don't poll. Use host.useToolData with a TTL (time to live, how long a cached value stays fresh).
  11. Call files.pick and notify.fix only from a user tap, never from a tool the agent calls.
  12. 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.

ThingFormExample
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 appmcp-<server name>mcp-demo-canteen
Document install linkGitHub 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 writeslocal 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 validate and test pass, and the declarations match what the steps do.
  • Code plugin: requires and scopes are complete, and defaultModes asks the user first for money and public.
  • 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 cloud declaration.
  • 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

URLUse
/llms.txtIndex of every page with a one-line description, for choosing what to load
/llms-full.txtEvery English page in one file (large)
/en/docs/<page>.mdOne page as Markdown
/en/docs/cloud/api/<tag>.mdCloud API operations and schemas as text

On this page