PKU harness
Cloud

PKU harness Cloud

PKU harness Cloud is an optional backend standard for plugins, covering storage, relay channels, push, files and server functions. The official instance, a self-hosted server or any conforming server can provide it. Data is end-to-end encrypted by default, and the cloud holds no campus identity.

Most plugins run only on the phone, but a few features need a server. For example, a plugin may share a campus-card payment code live with a friend who doesn't have the app, or keep a small piece of data in sync across devices. PKU harness Cloud ("the cloud" below) is the backend standard for these features. It defines which endpoints a server provides and how the app and plugins use them.

We designed the cloud so that you can use a server without having to trust it. The sections below explain how.

Design principles

Data is encrypted before it leaves the phone. This is called end-to-end (E2E) encryption: only the sender and the receiver hold the keys, and the server forwards and stores ciphertext that it can't decrypt.

The cloud holds no campus identity. The server knows a device only by a keypair that the device generates. It never learns your student ID or name, and it never receives your campus password or login sessions.

Several providers can serve the same protocol. You can use the official instance, a reference server you deploy yourself, or any server URL that implements the protocol. Plugins call the same interface in every case and can't tell which provider they are talking to.

The app keeps plugins apart. We call the app the host, because it runs the plugins. The host gives each plugin a cloud client bound to that plugin alone, with its own tokens, its own storage space and its own consent from you.

The AI assistant uses the cloud only through the tools that plugins provide. It never sees cloud credentials, tokens or share links.

Status (2026-10-24)

The official cloud instance is not deployed yet. Until it is, enter the URL of a self-hosted server in Settings → Cloud if you want to use the cloud.

These parts are done: protocol v1 (@pku-harness/cloud-protocol), the SDK types, the reference server (packages/cloud-server) and the cloud client in the app. The client handles device identity, per-plugin binding and consent, end-to-end encrypted store and channels, and a connection test in Settings → Cloud. The first plugin that uses the cloud is card-share, which shares a campus-card payment code live, end-to-end encrypted. The push service (push) isn't finished, and the reference server answers 501 unsupported for it.

When the official instance launches, it will stay small on purpose. It will offer the relay for live shares (channels and mailbox, where the server only forwards ciphertext it can't read), a small end-to-end encrypted store and the static viewer pages. Plugin server functions exist in the reference server, but they are off on the official server.

The three parts of the cloud

The cloud has three parts. The service definition consists of SDK types and a versioned HTTP protocol. Service providers are whoever runs a server. Consumers are the plugins that call the cloud.

PartWhatWhere
Service definitionSDK types (CloudManifest, PluginCloud) and the HTTP protocol (/v1)@pku-harness/sdk (cloud.ts), @pku-harness/cloud-protocol, API reference
Service providersThe official instance, the open-source reference server (self-hosted), any conforming URLProviders and self-hosting
ConsumersPlugins, through ctx.cloud or props.cloudPlugin guide

Services the cloud offers

The protocol defines five services. A provider may offer only some of them.

store is a key-value store. Each plugin on each device gets its own space, keyed by (device, plugin). It supports versions (If-Match) and expiry times (time to live, TTL). When a plugin declares e2e, values are encrypted on the phone before they are uploaded.

channels are relay channels for live sharing. A device opens a channel, viewers join with a share link, and the server relays data frames between them. Channels are always end-to-end encrypted, and each viewer's key is derived from the secret in the share link.

push handles push tokens and scheduled pushes. It is still planned, and the official instance won't offer it. Push payloads are encrypted, so the server only learns when to send.

files is file storage. It is also planned.

functions are server functions that a plugin declares in the server: section of its PLUGIN.md (see below). They are off on the official server. A server function must be declared in advance and approved by you, and it is the one place in the cloud where plaintext may be processed.

The app asks a provider which services it offers with GET /v1/meta. If a plugin lists a service in uses that the provider doesn't offer, the app marks the plugin unavailable and says why.

How a request flows

Take a plugin writing one value to the store as an example. The plugin calls ctx.cloud.store.put. The host first checks consent and the uses and e2e fields of the plugin's manifest. If the plugin declared e2e, the host encrypts the value with XChaCha20-Poly1305. The host then signs a challenge with the device's Ed25519 key to get a token valid for 10 minutes, and sends the request with that token to the provider. The provider reads the device and plugin from the token, picks the storage space, and stores the ciphertext.

plugin ──ctx.cloud.store.put(key, value)──► host (bound client for this plugin)
          │ checks consent, manifest `uses`, `e2e`
          │ seals value (XChaCha20-Poly1305) when e2e
          │ mints/refreshes a 10-min token (Ed25519-signed challenge)
          ▼
        provider  PUT /v1/store/<key>   Authorization: Bearer <token>
          │ namespace = (deviceId, plugin) from the token
          ▼
        stores ciphertext

Where server code runs

When a plugin needs a server, there are three options. We suggest considering them in this order.

  1. Standard services: store, channels, mailbox, push and files. Every conforming server implements them, and no plugin code runs on the server. Use this option whenever it can do the job.
  2. Plugin server functions, written in the server: section of a PLUGIN.md. The server runs them with the same interpreter as the app. They are off on the official server.
  3. External backends: any URL that implements the protocol. The plugin declares it in the manifest (backend: { kind: 'external', url }), and you must approve it before it is used.

Server functions

Off on the official server

Keep your plugin's work on the phone, or run your own backend. The design document, docs/CLOUD-FUNCTIONS.md, is on hold. What already exists stays, but collections, schedules, push, groups and webhooks are not being built.

A plugin document can declare server functions. They use the same steps and expressions as the plugin's tools, but they run on the server. Here is an example:

PLUGIN.md (frontmatter excerpt)
cloud:
  disclosure: { zh: ['你输入的名字会发到云端服务器。'], en: ['The name you enter is sent to the cloud server.'] }
server:
  network: [api.example.org]        # hosts server steps may call (HTTPS)
  secrets: [API_KEY]                # held by the operator; usable only in request templates
  functions:
    stats:
      effect: read
      access: public                # user (default) | public served today; peer, group, webhook are not
      cache: 30s
      steps:
        - let: c
          request: { url: 'https://api.example.org/v1/count', headers: { authorization: 'Bearer ${secrets.API_KEY}' } }
          as: json
      return: "{ count: c.count }"

Keep these rules in mind:

  • A server: section implies cloud.uses: [functions], and it requires cloud.disclosure.
  • Server steps have their own set of host abilities: a per-user server key-value store (kv.get/set/delete), plus crypto.sha256, crypto.hmacSha256 (which takes a secret by name) and crypto.randomId. Server steps can't reach campus services. caller.id is a pseudonym of the device, different for each plugin.
  • The phone calls a function through the cloud.call { fn, input } ability. A widget can bind { fn } as well as { tool }.
  • The token records the plugin document version you consented to (link plus content hash), and the server runs only that version. Whether it runs at all also depends on the operator's policy, CLOUD_FN_POLICY, which is verified, allowlist or open. The reference server's README lists the environment variables.

Next

On this page