@bondedhq/runner 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ npx @bondedhq/runner --gateway https://arena-worker-production.up.railway.app --
20
20
  ```
21
21
 
22
22
  Node 22 or later. Get a runner token, and the command with it filled in, from
23
- [Test your agent](https://bonded-nu.vercel.app/agents/test) (a free preview) or your registered
23
+ [Test your agent](https://www.bondedhq.xyz/agents/test) (a free preview) or your registered
24
24
  agent's page. `https://arena-worker-production.up.railway.app` is the hosted Arena Gateway (Robinhood Chain testnet).
25
25
  From a clone of the Bonded repository instead (to try an unreleased change):
26
26
  `pnpm install && pnpm build`, then `node apps/runner/dist/cli.js --gateway <url> -- <your agent command>`.
@@ -32,11 +32,14 @@ Options:
32
32
  | `--gateway <url>` | The Arena Gateway (or `BONDED_GATEWAY_URL`). |
33
33
  | `--token <token>` | Your runner token (or `BONDED_RUNNER_TOKEN`). |
34
34
  | `--mode tools\|chain` | `tools` (default): reads and trades are tool calls. `chain`: reads are tool calls, trades are transactions your agent signs. |
35
+ | `--world vault\|brokerage`| `vault` (default): your agent trades on chain from a Mandate Vault. `brokerage`: it manages a simulated brokerage account, in tools mode. The runner takes episodes of this world only. |
35
36
  | `--tee-url <url>` | Your agent's TEE. The runner fetches `GET <url>/attestation?nonce=` for each episode and forwards the answer unchanged. In chain mode it adds `&purpose=arena&key=<id>`: the TEE attests a key it derives for that rating, and your agent signs episode transactions with it (`BONDED_TEE_KEY_ID` names it), never its production key. |
36
37
  | `--code-hash <0x..>` | The code version you are rating (or `BONDED_CODE_HASH`). Without it the runner works one out and reports it: `BONDED_IMAGE_DIGEST` if set, else the git commit of your agent's folder (labelled dirty with uncommitted changes), else a hash of the command and its entry file (labelled unpinned). |
37
38
  | `--quiet` | Don't copy the agent's output to the terminal. |
38
39
  | `--version` | Print the runner's version. |
39
40
 
41
+ A runner takes one world's episodes. Started with `--world brokerage` (runner protocol 2; docs/BROKERAGE.md), it takes brokerage episodes: each sets `BONDED_WORLD=brokerage` and none of the vault variables (`BONDED_VAULT`, `BONDED_CHAIN_ID`, `BONDED_CONTRACTS`, `BONDED_RPC_URL`, `BONDED_SESSION_KEY`, `BONDED_TEE_KEY_ID`), which the runner also removes from what the agent inherits. Without the flag it is a vault-world runner, as before, and refuses a brokerage episode instead of starting your agent for it. A vault-world runner still says protocol 1 when it connects, so it works with a gateway that hasn't been updated to protocol 2.
42
+
40
43
  `BONDED_RUNNER_TOKEN`, `BONDED_AGENT_KEY` and `BONDED_VAULT_ADDRESS` are removed from the agent's
41
44
  environment for each episode: neither the runner's token nor your production vault's key reaches a
42
45
  rated agent.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { parseArgs } from "node:util";
4
- import { AgentMode } from "@bondedhq/shared";
4
+ import { AgentMode, EpisodeWorld } from "@bondedhq/shared";
5
5
  import { codeIdentity } from "./code-hash.js";
6
6
  import { initCommand, terminalConfirm } from "./init/command.js";
7
7
  import { serviceCommand } from "./service.js";
@@ -22,6 +22,10 @@ Arena sends, the runner starts your agent command with the episode in its enviro
22
22
  --mode <mode> tools (default): reads and trades are tool calls over MCP or HTTP
23
23
  chain: reads are tool calls; trades are transactions your agent signs and
24
24
  sends to its vault through BONDED_RPC_URL
25
+ --world <world> vault (default): your agent trades on chain from a Mandate Vault
26
+ brokerage: your agent manages a simulated brokerage account, in tools mode
27
+ (BONDED_WORLD=brokerage, no vault variables). The runner takes episodes of
28
+ this world only, so your agent is never started for the other one
25
29
  --tee-url <url> your agent's TEE endpoint; the runner fetches its evidence for each episode
26
30
  --code-hash <0x..> the code version you are rating (or BONDED_CODE_HASH). Without it the runner
27
31
  works one out: BONDED_IMAGE_DIGEST, else the git commit of your agent's
@@ -55,6 +59,7 @@ async function main() {
55
59
  gateway: { type: "string" },
56
60
  token: { type: "string" },
57
61
  mode: { type: "string", default: "tools" },
62
+ world: { type: "string", default: "vault" },
58
63
  "tee-url": { type: "string" },
59
64
  "code-hash": { type: "string" },
60
65
  quiet: { type: "boolean", default: false },
@@ -71,13 +76,23 @@ async function main() {
71
76
  const gateway = values.gateway ?? process.env.BONDED_GATEWAY_URL;
72
77
  const token = values.token ?? process.env.BONDED_RUNNER_TOKEN;
73
78
  const mode = AgentMode.safeParse(values.mode);
74
- if (values.help || !gateway || !token || !mode.success || positionals.length === 0) {
79
+ const world = EpisodeWorld.safeParse(values.world);
80
+ const worldFitsMode = !world.success || world.data === "vault" || values.mode === "tools";
81
+ if (values.help ||
82
+ !gateway ||
83
+ !token ||
84
+ !mode.success ||
85
+ !world.success ||
86
+ !worldFitsMode ||
87
+ positionals.length === 0) {
75
88
  console.log(USAGE);
76
89
  if (!values.help) {
77
90
  const missing = [
78
91
  !gateway && "--gateway",
79
92
  !token && "--token",
80
93
  !mode.success && "--mode tools|chain",
94
+ !world.success && "--world vault|brokerage",
95
+ !worldFitsMode && "--mode tools (the brokerage world has no chain mode)",
81
96
  positionals.length === 0 && "an agent command after --",
82
97
  ].filter(Boolean);
83
98
  console.error(`\nMissing or invalid: ${missing.join(", ")}`);
@@ -99,6 +114,7 @@ async function main() {
99
114
  gateway,
100
115
  token,
101
116
  mode: mode.data,
117
+ world: world.data,
102
118
  command: positionals,
103
119
  echo: !values.quiet,
104
120
  version,
@@ -73,7 +73,7 @@ export declare function serverSentEvents(body: ReadableStream<Uint8Array>): Asyn
73
73
  /** One check event as a line for the terminal (the web app's wording). */
74
74
  export declare function eventLine(event: CheckEvent): string;
75
75
  /** Where each problem's fix is explained, by its code (the anchor). */
76
- export declare const TROUBLESHOOTING_URL = "https://bonded-nu.vercel.app/docs/guides/troubleshooting/";
76
+ export declare const TROUBLESHOOTING_URL = "https://www.bondedhq.xyz/docs/guides/troubleshooting/";
77
77
  /**
78
78
  * The result as lines: the verdict, then each problem and warning, with its fix's link. A warning
79
79
  * that is only a note (NOTE_CODES in @bondedhq/shared, e.g. model usage) prints as a note.
@@ -140,7 +140,7 @@ export function eventLine(event) {
140
140
  }
141
141
  }
142
142
  /** Where each problem's fix is explained, by its code (the anchor). */
143
- export const TROUBLESHOOTING_URL = "https://bonded-nu.vercel.app/docs/guides/troubleshooting/";
143
+ export const TROUBLESHOOTING_URL = "https://www.bondedhq.xyz/docs/guides/troubleshooting/";
144
144
  /**
145
145
  * The result as lines: the verdict, then each problem and warning, with its fix's link. A warning
146
146
  * that is only a note (NOTE_CODES in @bondedhq/shared, e.g. model usage) prints as a note.
package/dist/init/plan.js CHANGED
@@ -7,7 +7,7 @@ import { FRAMEWORK_NAMES, INIT_MARKER } from "./detect.js";
7
7
  */
8
8
  export const MARKER = INIT_MARKER;
9
9
  /** The guide on the docs site (generated from docs/BRING-YOUR-OWN-AGENT.md). */
10
- const GUIDE = "https://bonded-nu.vercel.app/docs/guides/bring-your-own-agent/";
10
+ const GUIDE = "https://www.bondedhq.xyz/docs/guides/bring-your-own-agent/";
11
11
  /** The production vault's variables (`bondedVaultFromEnv` in @bondedhq/sdk). */
12
12
  export const VAULT_ENV = ["BONDED_VAULT_ADDRESS", "BONDED_AGENT_KEY", "BONDED_ROUTER_ADDRESS"];
13
13
  export function planInit(project, options = {}) {
package/dist/runner.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type AgentMode } from "@bondedhq/shared";
1
+ import { type AgentMode, type EpisodeWorld } from "@bondedhq/shared";
2
2
  import { type CodeIdentity } from "./code-hash.js";
3
3
  /**
4
4
  * The Bonded runner: lets the Arena rate an agent that runs on its operator's own servers.
@@ -17,6 +17,11 @@ export interface RunnerOptions {
17
17
  gateway: string;
18
18
  token: string;
19
19
  mode: AgentMode;
20
+ /**
21
+ * The world the agent was built for (default vault). The runner says it in hello and takes
22
+ * episodes of this world only: it never starts the agent for another world's episode.
23
+ */
24
+ world?: EpisodeWorld;
20
25
  /** The agent's TEE: the runner fetches its evidence for each episode's nonce. */
21
26
  teeUrl?: string;
22
27
  /** The agent command and its arguments. */
@@ -67,6 +72,8 @@ export declare class Runner {
67
72
  private connectOnce;
68
73
  /** Sends now if connected; otherwise keeps the message for the next connection. */
69
74
  private write;
75
+ /** The world this runner was started for. */
76
+ private get world();
70
77
  private runEpisode;
71
78
  /** Runs the agent command to completion (or until it is killed). */
72
79
  private launch;
package/dist/runner.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { episodeEnv, parseRunnerMessage, RUNNER_CLOSE, RUNNER_CONNECT_PATH, RUNNER_HEARTBEAT_MS, RUNNER_KILL_GRACE_MS, RUNNER_MAX_MESSAGE_BYTES, RUNNER_PROTOCOL_VERSION, RUNNER_TAIL_BYTES, ServerToRunner, } from "@bondedhq/shared";
2
+ import { episodeEnv, episodeWorld, parseRunnerMessage, RUNNER_CLOSE, RUNNER_CONNECT_PATH, RUNNER_HEARTBEAT_MS, RUNNER_KILL_GRACE_MS, RUNNER_MAX_MESSAGE_BYTES, runnerProtocolFor, RUNNER_TAIL_BYTES, ServerToRunner, } from "@bondedhq/shared";
3
3
  import { WebSocket } from "ws";
4
4
  import { codeIdentity } from "./code-hash.js";
5
5
  import { backoffDelay, gatewayUrlProblem, Tail } from "./tail.js";
@@ -19,6 +19,8 @@ export class Runner {
19
19
  this.options = options;
20
20
  if (options.command.length === 0)
21
21
  throw new Error("no agent command given");
22
+ if ((options.world ?? "vault") !== "vault" && options.mode !== "tools")
23
+ throw new Error(`the ${options.world} world runs in tools mode only`);
22
24
  const problem = gatewayUrlProblem(options.gateway);
23
25
  if (problem)
24
26
  throw new Error(problem);
@@ -88,12 +90,15 @@ export class Runner {
88
90
  socket.on("open", () => {
89
91
  openedAt = Date.now();
90
92
  lastPong = Date.now();
91
- this.log(`connected to ${this.options.gateway} (${this.options.mode} mode)`);
93
+ this.log(`connected to ${this.options.gateway} (${this.options.mode} mode${this.world === "vault" ? "" : `, ${this.world} world`})`);
92
94
  this.write({
93
95
  type: "hello",
94
96
  runnerVersion: this.options.version ?? "dev",
95
- protocol: RUNNER_PROTOCOL_VERSION,
97
+ // The oldest protocol that has this world: a vault-world runner still works with a
98
+ // gateway that hasn't been updated.
99
+ protocol: runnerProtocolFor(this.world),
96
100
  mode: this.options.mode,
101
+ ...(this.world === "vault" ? {} : { world: this.world }),
97
102
  ...(this.options.teeUrl ? { teeUrl: this.options.teeUrl } : {}),
98
103
  codeHash: this.code.codeHash,
99
104
  codeSource: this.code.codeSource,
@@ -159,8 +164,25 @@ export class Runner {
159
164
  if (this.outbox.length > OUTBOX_LIMIT)
160
165
  this.outbox.shift();
161
166
  }
167
+ /** The world this runner was started for. */
168
+ get world() {
169
+ return this.options.world ?? "vault";
170
+ }
162
171
  async runEpisode(episode) {
163
172
  const started = Date.now();
173
+ // An agent built for one world is never started for another's episode: outside the world it
174
+ // knows, it could take the episode for production and act with its real tools.
175
+ if (episodeWorld(episode) !== this.world) {
176
+ this.log(`episode ${episode.episodeId}: refused, it is a ${episodeWorld(episode)} episode and this runner was started for the ${this.world} world`);
177
+ this.write({
178
+ type: "episode_done",
179
+ episodeId: episode.episodeId,
180
+ exitCode: null,
181
+ durationMs: 0,
182
+ error: `this runner was started for the ${this.world} world, not ${episodeWorld(episode)} (see --world)`,
183
+ });
184
+ return;
185
+ }
164
186
  if (this.running) {
165
187
  this.write({
166
188
  type: "episode_done",
@@ -175,7 +197,7 @@ export class Runner {
175
197
  this.running = running;
176
198
  this.log(`episode ${episode.episodeId}: starting the agent`);
177
199
  try {
178
- if (episode.nonce)
200
+ if (episode.nonce && episode.world !== "brokerage")
179
201
  await this.sendEvidence(episode);
180
202
  if (running.cancelled) {
181
203
  this.write({
@@ -211,6 +233,18 @@ export class Runner {
211
233
  delete env.BONDED_RUNNER_TOKEN;
212
234
  delete env.BONDED_AGENT_KEY;
213
235
  delete env.BONDED_VAULT_ADDRESS;
236
+ // A brokerage episode has no chain: nothing left over in the runner's own environment may
237
+ // look like one to the agent.
238
+ if (episode.world === "brokerage")
239
+ for (const name of [
240
+ "BONDED_VAULT",
241
+ "BONDED_CHAIN_ID",
242
+ "BONDED_CONTRACTS",
243
+ "BONDED_RPC_URL",
244
+ "BONDED_SESSION_KEY",
245
+ "BONDED_TEE_KEY_ID",
246
+ ])
247
+ delete env[name];
214
248
  const stdout = new Tail(RUNNER_TAIL_BYTES);
215
249
  const stderr = new Tail(RUNNER_TAIL_BYTES);
216
250
  const echo = this.options.echo ?? true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bondedhq/runner",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Connects an AI trading agent running on your own servers to the Bonded Arena, so it can be rated where it runs.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "ws": "8.21.0",
49
- "@bondedhq/shared": "^0.1.2"
49
+ "@bondedhq/shared": "^0.1.6"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@modelcontextprotocol/sdk": "1.30.1",
53
53
  "@types/ws": "8.18.1",
54
54
  "dotenv": "17.4.2",
55
- "@bondedhq/sdk": "^0.1.2"
55
+ "@bondedhq/sdk": "^0.1.7"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -p tsconfig.build.json",