@upoch/guard 0.1.1 → 0.1.3
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 +28 -4
- package/dist/cli.js +3 -6
- package/dist/guard.d.ts +33 -6
- package/dist/guard.js +70 -5
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -6,9 +6,15 @@ Upoch Guard saves verifiable checkpoints of an agent's work on Robinhood Chain.
|
|
|
6
6
|
prompt or makes a bad trade, it restores the last good state, checked against the chain, or rolls back to the
|
|
7
7
|
checkpoint before the mistake and keeps going. It also knows which of its actions already happened.
|
|
8
8
|
|
|
9
|
-
This package is the `upoch` command you set Upoch up with, and the engine behind the Upoch
|
|
9
|
+
This package is the `upoch` command you set Upoch up with, an MCP server, and the engine behind the Upoch
|
|
10
|
+
framework plugins:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
| Agent | One line |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Claude Code | `claude mcp add upoch -- upoch mcp` |
|
|
15
|
+
| Cursor, Codex, Claude Desktop, any MCP client | an MCP server with the command `upoch mcp` (stdio) |
|
|
16
|
+
| ElizaOS | `plugins: ["@upoch/plugin-elizaos"]` in your character ([@upoch/plugin-elizaos](https://www.npmjs.com/package/@upoch/plugin-elizaos)) |
|
|
17
|
+
| LangChain / LangGraph | `callbacks: [new UpochCallbackHandler()]` ([@upoch/langchain](https://www.npmjs.com/package/@upoch/langchain)) |
|
|
12
18
|
|
|
13
19
|
Website and debugger: https://upoch.tech. Contract (Robinhood Chain, 4663): `0x6179d9123c3c1f9e82f948101a4350cb55aa8c27`
|
|
14
20
|
|
|
@@ -22,8 +28,8 @@ upoch setup
|
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
`upoch setup` creates your keys in `~/.upoch`, shows the owner address, and waits while you send it a little ETH on
|
|
25
|
-
Robinhood Chain (0.001 ETH is plenty). Then it registers your agent
|
|
26
|
-
|
|
31
|
+
Robinhood Chain (0.001 ETH is plenty). Then it registers your agent and, if Claude Code is installed, offers to
|
|
32
|
+
add the MCP server to it. After that, connecting an agent is one line (table above).
|
|
27
33
|
|
|
28
34
|
Prefer separate steps? Run `upoch init`, then `upoch register`.
|
|
29
35
|
|
|
@@ -42,10 +48,28 @@ Two keys are created on your machine and never leave it:
|
|
|
42
48
|
A rollback restores the agent's state and memory. It cannot reverse transactions that already happened on-chain;
|
|
43
49
|
those are listed so the agent can account for them.
|
|
44
50
|
|
|
51
|
+
## MCP tools
|
|
52
|
+
|
|
53
|
+
| Tool | What it does | On-chain |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `upoch_restore` | Restores the latest checkpoint after verifying it: state, note, completed and unresolved actions | read |
|
|
56
|
+
| `upoch_checkpoint` | Saves the current state as a checkpoint (`progress`, `before_action`, `after_action`, `manual`) | 1 transaction |
|
|
57
|
+
| `upoch_record_action` | Records an external action (planned, submitted, confirmed, failed) with its transaction hash | no (goes into the next checkpoint) |
|
|
58
|
+
| `upoch_rollback` | Goes back to an earlier checkpoint (e.g. before a bad trade) and continues from it; lists actions since then that were not undone on-chain | 1 transaction |
|
|
59
|
+
| `upoch_complete` | Saves a final checkpoint and marks the run finished | 1 transaction |
|
|
60
|
+
| `upoch_history` | Lists recent checkpoints | read |
|
|
61
|
+
| `upoch_verify` | Verifies one checkpoint without restoring it | read |
|
|
62
|
+
| `upoch_status` | Shows the workflow, executor funds and today's usage | read |
|
|
63
|
+
|
|
64
|
+
The server tells the agent when to use them: restore at the start, checkpoint after meaningful progress and before
|
|
65
|
+
consequential actions, record each action as planned and then as confirmed, roll back when something went wrong,
|
|
66
|
+
and complete at the end.
|
|
67
|
+
|
|
45
68
|
## Commands
|
|
46
69
|
|
|
47
70
|
```sh
|
|
48
71
|
upoch setup # keys, funding, registration, in one go
|
|
72
|
+
upoch mcp # run the MCP server (stdio); your MCP client starts it
|
|
49
73
|
upoch status # workflow, funds and today's usage
|
|
50
74
|
upoch key # print the checkpoint key, to open checkpoints in the debugger
|
|
51
75
|
upoch export-owner # print the owner private key, to import it into a wallet
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// upoch init create keys and settings (your terminal only)
|
|
5
5
|
// upoch register register the agent's workflow on-chain and fund its executor (your terminal only)
|
|
6
6
|
// upoch status show the workflow, funds and usage
|
|
7
|
-
// upoch mcp run the MCP server (stdio) for Claude Code, Cursor, Codex and other MCP clients
|
|
7
|
+
// upoch mcp run the MCP server (stdio) for Claude Code, Cursor, Codex and other MCP clients
|
|
8
8
|
// upoch key print the checkpoint key, to open checkpoints in the debugger (your terminal only)
|
|
9
9
|
// upoch export-owner print the owner private key, to import it into a wallet (your terminal only)
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -16,11 +16,8 @@ import { hasSetup, loadConfig, loadSecrets, networkInfo, upochHome } from "./con
|
|
|
16
16
|
import { Guard } from "./guard.js";
|
|
17
17
|
import { createServer, VERSION } from "./server.js";
|
|
18
18
|
import { initialize, register, registrationPlan } from "./setup.js";
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
* them only once they launch. Flip these at launch.
|
|
22
|
-
*/
|
|
23
|
-
const LAUNCHED = { mcp: false, langchain: false };
|
|
19
|
+
/** Integrations listed in help and setup output. */
|
|
20
|
+
const LAUNCHED = { mcp: true, langchain: true };
|
|
24
21
|
const CONNECT = [
|
|
25
22
|
...(LAUNCHED.mcp ? [" Claude Code claude mcp add upoch -- upoch mcp", " Cursor, Codex add an MCP server with the command: upoch mcp"] : []),
|
|
26
23
|
' ElizaOS plugins: ["@upoch/plugin-elizaos"] in your character',
|
package/dist/guard.d.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
|
-
import { LocalKeySigner, type Eip1193Provider } from "@upoch/sdk";
|
|
1
|
+
import { LocalKeySigner, type Eip1193Provider, type StorageAdapter } from "@upoch/sdk";
|
|
2
2
|
import { type NetworkInfo, type UpochConfig } from "./config.ts";
|
|
3
3
|
export type CheckpointKind = "progress" | "before_action" | "after_action" | "manual";
|
|
4
4
|
export type ActionStatus = "planned" | "submitted" | "confirmed" | "failed";
|
|
5
|
+
/** An action that ran after a rollback's target checkpoint; rolling back does not reverse it. */
|
|
6
|
+
export type NotUndone = {
|
|
7
|
+
operationId: string;
|
|
8
|
+
status: string;
|
|
9
|
+
transactionHash?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* IPFS that reads through the gateway and, when the gateway refuses or fails, through the authenticated RPC `cat`.
|
|
13
|
+
* Found on mainnet: Filebase's gateway intermittently answers 403 "CDN proxying is not allowed" (behind VPNs such as
|
|
14
|
+
* Cloudflare WARP), which would otherwise fail a restore or the verification after a commit.
|
|
15
|
+
*/
|
|
16
|
+
export declare class IpfsWithFallback implements StorageAdapter {
|
|
17
|
+
readonly provider: "ipfs";
|
|
18
|
+
readonly developmentOnly = false;
|
|
19
|
+
private readonly gateway;
|
|
20
|
+
private readonly rpc;
|
|
21
|
+
constructor(o: {
|
|
22
|
+
apiUrl: string;
|
|
23
|
+
gatewayUrl: string;
|
|
24
|
+
authorization?: string;
|
|
25
|
+
});
|
|
26
|
+
upload(bytes: Uint8Array): Promise<import("@upoch/sdk").StoredObject>;
|
|
27
|
+
retrieve(contentId: string): Promise<Uint8Array<ArrayBufferLike>>;
|
|
28
|
+
isAvailable(contentId: string): Promise<boolean>;
|
|
29
|
+
health(): Promise<import("@upoch/sdk").StorageHealth>;
|
|
30
|
+
locator(contentId: string): string;
|
|
31
|
+
}
|
|
5
32
|
/** A refusal the agent can act on (wrong input, a limit, missing funds). Distinct from unexpected failures. */
|
|
6
33
|
export declare class GuardError extends Error {
|
|
7
34
|
readonly code: string;
|
|
@@ -12,6 +39,8 @@ export interface GuardOptions {
|
|
|
12
39
|
/** Overrides the RPC provider (tests). */
|
|
13
40
|
provider?: Eip1193Provider;
|
|
14
41
|
now?: () => Date;
|
|
42
|
+
/** Waits before retrying a checkpoint whose submission failed transiently (default 3 s, then 8 s). */
|
|
43
|
+
retryDelaysMs?: number[];
|
|
15
44
|
}
|
|
16
45
|
export declare class Guard {
|
|
17
46
|
readonly home: string;
|
|
@@ -23,6 +52,7 @@ export declare class Guard {
|
|
|
23
52
|
private readonly reader;
|
|
24
53
|
private readonly journal;
|
|
25
54
|
private readonly now;
|
|
55
|
+
private readonly retryDelaysMs;
|
|
26
56
|
private readonly principal;
|
|
27
57
|
private parts?;
|
|
28
58
|
constructor(o?: GuardOptions);
|
|
@@ -69,6 +99,7 @@ export declare class Guard {
|
|
|
69
99
|
state: unknown;
|
|
70
100
|
note?: string;
|
|
71
101
|
kind?: CheckpointKind;
|
|
102
|
+
notUndone?: NotUndone[];
|
|
72
103
|
}, executionStatus?: "RUNNING" | "COMPLETED"): Promise<any>;
|
|
73
104
|
/** Records an external action (a swap, a transfer, an API call) so the next checkpoint and any recovery know about it. */
|
|
74
105
|
recordAction(a: {
|
|
@@ -101,11 +132,7 @@ export declare class Guard {
|
|
|
101
132
|
rollback(o: {
|
|
102
133
|
sequence: number;
|
|
103
134
|
reason: string;
|
|
104
|
-
annotate?: (state: unknown, actionsSince:
|
|
105
|
-
operationId: string;
|
|
106
|
-
status: string;
|
|
107
|
-
transactionHash?: string;
|
|
108
|
-
}[]) => unknown;
|
|
135
|
+
annotate?: (state: unknown, actionsSince: NotUndone[]) => unknown;
|
|
109
136
|
}): Promise<any>;
|
|
110
137
|
/** The most recent checkpoints on-chain, newest first. Read-only. */
|
|
111
138
|
history(o?: {
|
package/dist/guard.js
CHANGED
|
@@ -10,6 +10,52 @@ const CHECKPOINT_GAS = 230000n;
|
|
|
10
10
|
const HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
11
11
|
const TYPES = { progress: "SAFE_BOUNDARY", before_action: "PRE_ACTION", after_action: "POST_ACTION", manual: "MANUAL" };
|
|
12
12
|
const STATUSES = { planned: "PENDING", submitted: "SUBMITTED", confirmed: "CONFIRMED", failed: "FAILED" };
|
|
13
|
+
/**
|
|
14
|
+
* IPFS that reads through the gateway and, when the gateway refuses or fails, through the authenticated RPC `cat`.
|
|
15
|
+
* Found on mainnet: Filebase's gateway intermittently answers 403 "CDN proxying is not allowed" (behind VPNs such as
|
|
16
|
+
* Cloudflare WARP), which would otherwise fail a restore or the verification after a commit.
|
|
17
|
+
*/
|
|
18
|
+
export class IpfsWithFallback {
|
|
19
|
+
provider = "ipfs";
|
|
20
|
+
developmentOnly = false;
|
|
21
|
+
gateway;
|
|
22
|
+
rpc;
|
|
23
|
+
constructor(o) {
|
|
24
|
+
this.gateway = new IpfsStorageAdapter(o);
|
|
25
|
+
this.rpc = new IpfsStorageAdapter({ apiUrl: o.apiUrl, ...(o.authorization ? { authorization: o.authorization } : {}) });
|
|
26
|
+
}
|
|
27
|
+
upload(bytes) {
|
|
28
|
+
return this.rpc.upload(bytes);
|
|
29
|
+
}
|
|
30
|
+
async retrieve(contentId) {
|
|
31
|
+
try {
|
|
32
|
+
return await this.gateway.retrieve(contentId);
|
|
33
|
+
}
|
|
34
|
+
catch (gatewayError) {
|
|
35
|
+
try {
|
|
36
|
+
return await this.rpc.retrieve(contentId);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw gatewayError;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async isAvailable(contentId) {
|
|
44
|
+
try {
|
|
45
|
+
await this.retrieve(contentId);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
health() {
|
|
53
|
+
return this.rpc.health();
|
|
54
|
+
}
|
|
55
|
+
locator(contentId) {
|
|
56
|
+
return this.gateway.locator(contentId);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
13
59
|
/** A refusal the agent can act on (wrong input, a limit, missing funds). Distinct from unexpected failures. */
|
|
14
60
|
export class GuardError extends Error {
|
|
15
61
|
code;
|
|
@@ -29,6 +75,7 @@ export class Guard {
|
|
|
29
75
|
reader;
|
|
30
76
|
journal;
|
|
31
77
|
now;
|
|
78
|
+
retryDelaysMs;
|
|
32
79
|
principal;
|
|
33
80
|
parts;
|
|
34
81
|
constructor(o = {}) {
|
|
@@ -42,6 +89,7 @@ export class Guard {
|
|
|
42
89
|
this.reader = new EpochReader(this.provider, this.network.ledger);
|
|
43
90
|
this.journal = new FileJournal(journalPath(this.home));
|
|
44
91
|
this.now = o.now ?? (() => new Date());
|
|
92
|
+
this.retryDelaysMs = o.retryDelaysMs ?? [3000, 8000];
|
|
45
93
|
this.principal = { kind: "agent", id: this.config.agentId };
|
|
46
94
|
// Built lazily (needs a registered workflow); the secrets stay inside these closures, never on `this`.
|
|
47
95
|
this.build = () => {
|
|
@@ -52,7 +100,9 @@ export class Guard {
|
|
|
52
100
|
const s = this.config.storage;
|
|
53
101
|
const storage = new ReplicatedStorage([
|
|
54
102
|
s.kind === "ipfs"
|
|
55
|
-
?
|
|
103
|
+
? s.gatewayUrl
|
|
104
|
+
? new IpfsWithFallback({ apiUrl: s.apiUrl, gatewayUrl: s.gatewayUrl, ...(secrets.ipfsAuthorization ? { authorization: secrets.ipfsAuthorization } : {}) })
|
|
105
|
+
: new IpfsStorageAdapter({ apiUrl: s.apiUrl, authorization: secrets.ipfsAuthorization })
|
|
56
106
|
: new LocalStorageAdapter(s.dir),
|
|
57
107
|
]);
|
|
58
108
|
const encryption = new LocalAesGcmProvider(Buffer.from(secrets.encryptionKey, "hex"));
|
|
@@ -146,15 +196,24 @@ export class Guard {
|
|
|
146
196
|
...(r.transactionHash ? { transactionHash: r.transactionHash } : {}),
|
|
147
197
|
...(r.resultCommitment ? { resultHash: r.resultCommitment } : {}),
|
|
148
198
|
}));
|
|
149
|
-
const
|
|
199
|
+
const create = () => this.p.manager.create({
|
|
150
200
|
executionId: exec.executionId,
|
|
151
201
|
checkpointSequence: exec.nextSequence,
|
|
152
202
|
checkpointType: TYPES[input.kind ?? "progress"],
|
|
153
203
|
state: input.state,
|
|
154
|
-
context: input.note ? { note: input.note } : {},
|
|
204
|
+
context: { ...(input.note ? { note: input.note } : {}), ...(input.notUndone?.length ? { notUndone: input.notUndone } : {}) },
|
|
155
205
|
actions,
|
|
156
206
|
executionStatus,
|
|
157
207
|
});
|
|
208
|
+
let rec = await create();
|
|
209
|
+
// Found on mainnet: a transient RPC failure fails the submission before anything is sent. Retrying is safe: each
|
|
210
|
+
// attempt re-reads the head, and the chain adapter checks for an identical committed checkpoint before sending.
|
|
211
|
+
for (const wait of this.retryDelaysMs) {
|
|
212
|
+
if (rec.status !== "SUBMISSION_FAILED" || rec.error?.code !== "CHAIN_SUBMISSION_FAILED")
|
|
213
|
+
break;
|
|
214
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
215
|
+
rec = await create();
|
|
216
|
+
}
|
|
158
217
|
const committed = rec.status === "COMMITTED" || rec.status === "VERIFIED_RECOVERY_POINT";
|
|
159
218
|
if (committed) {
|
|
160
219
|
const after = this.session();
|
|
@@ -221,6 +280,9 @@ export class Guard {
|
|
|
221
280
|
saveSession(cp.executionStatus === "RUNNING" ? { ...s, executionId: cp.manifest.executionId, nextSequence: cp.manifest.checkpointSequence + 1 } : { day: s.day, checkpointsToday: s.checkpointsToday }, this.home);
|
|
222
281
|
}
|
|
223
282
|
const note = cp.context?.note;
|
|
283
|
+
// Saved by a rollback: actions that happened after the restored checkpoint, so a restart still knows them.
|
|
284
|
+
const notUndone = cp.context?.notUndone;
|
|
285
|
+
const hasNotUndone = Array.isArray(notUndone) && notUndone.length > 0;
|
|
224
286
|
return bigintSafe({
|
|
225
287
|
status: r.status,
|
|
226
288
|
sequence: cp.sequence,
|
|
@@ -233,11 +295,14 @@ export class Guard {
|
|
|
233
295
|
actions: cp.manifest.actions,
|
|
234
296
|
completedActions: r.completed.map((c) => c.operationId),
|
|
235
297
|
unresolvedActions: r.unresolved,
|
|
298
|
+
...(hasNotUndone ? { notUndone } : {}),
|
|
236
299
|
...(isHead ? {} : { warning: "This is an older checkpoint, read for inspection only. To go back to it and continue from it, call upoch_rollback with this sequence." }),
|
|
237
300
|
next: r.status === "HALTED_FOR_REVIEW"
|
|
238
301
|
? "Some actions have an unknown outcome. Check them (for example by transaction hash) before doing anything else, then record their real status."
|
|
239
302
|
: cp.executionStatus === "RUNNING"
|
|
240
|
-
?
|
|
303
|
+
? hasNotUndone
|
|
304
|
+
? "Continue from this state. It was restored by a rollback: the actions in notUndone happened after it and were NOT undone on-chain. Do not repeat them; check their effects (balances, positions) before acting."
|
|
305
|
+
: "Continue from this state. Do not repeat completed actions."
|
|
241
306
|
: "That run had finished. A new checkpoint starts a new run.",
|
|
242
307
|
});
|
|
243
308
|
}
|
|
@@ -282,7 +347,7 @@ export class Guard {
|
|
|
282
347
|
const note = `Rolled back to checkpoint #${o.sequence}: ${o.reason.trim()}`.slice(0, 500);
|
|
283
348
|
const actionsSince = [...since.values()];
|
|
284
349
|
const state = o.annotate ? o.annotate(structuredClone(cp.state), actionsSince) : cp.state;
|
|
285
|
-
const committed = await this.checkpoint({ state, note, kind: "manual" });
|
|
350
|
+
const committed = await this.checkpoint({ state, note, kind: "manual", notUndone: actionsSince });
|
|
286
351
|
if (committed.status !== "VERIFIED_RECOVERY_POINT" && committed.status !== "COMMITTED")
|
|
287
352
|
return { ...committed, rolledBackTo: o.sequence, state: undefined };
|
|
288
353
|
return bigintSafe({
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { Guard } from "./guard.ts";
|
|
3
|
-
export declare const VERSION = "0.1.
|
|
3
|
+
export declare const VERSION = "0.1.3";
|
|
4
4
|
export declare const INSTRUCTIONS = "Upoch Guard saves verifiable checkpoints of your work on Robinhood Chain, so a crash, a bad prompt or a bad trade can be recovered from the last good state.\n\nWhen to use it:\n- At the start of a task, call upoch_restore. If a run was interrupted, continue from the returned state and do not repeat its completed actions.\n- After meaningful progress, call upoch_checkpoint with the state you would need to continue (plan, step, positions, results so far). Not after every small step: each checkpoint is an on-chain transaction.\n- Before a consequential external action (a swap, a transfer, a deployment), call upoch_record_action with status \"planned\" and then upoch_checkpoint with kind \"before_action\". After it settles, record it again as \"confirmed\" (with its transaction hash) or \"failed\".\n- If something went wrong (a bad trade, a bad instruction, corrupted state), use upoch_history to find the last good checkpoint and upoch_rollback to go back to it and continue. Rolling back restores your state; it does not undo on-chain transactions, so check the actions it lists.\n- When the task is finished, call upoch_complete.\n\nNever put private keys, seed phrases, passwords or API tokens in the state, notes or actions: checkpoints are permanent. Upoch refuses state that looks like it contains them. Setup (keys, registration, funding) is done by the user in their own terminal with the upoch command; never run \"upoch init\", \"upoch register\", \"upoch key\" or \"upoch export-owner\" yourself.";
|
|
5
5
|
export declare function createServer(guard: Guard | (() => Guard)): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { Guard, GuardError } from "./guard.js";
|
|
6
|
-
export const VERSION = "0.1.
|
|
6
|
+
export const VERSION = "0.1.3";
|
|
7
7
|
export const INSTRUCTIONS = `Upoch Guard saves verifiable checkpoints of your work on Robinhood Chain, so a crash, a bad prompt or a bad trade can be recovered from the last good state.
|
|
8
8
|
|
|
9
9
|
When to use it:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upoch/guard",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Upoch Guard: verifiable checkpoints, recovery and rollback for AI agents on Robinhood Chain. The upoch command and the engine behind the Upoch
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Upoch Guard: verifiable checkpoints, recovery and rollback for AI agents on Robinhood Chain. The upoch command, an MCP server for Claude Code, Cursor and Codex, and the engine behind the Upoch plugins for ElizaOS and LangChain.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"upoch",
|
|
7
7
|
"ai-agents",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"recovery",
|
|
10
10
|
"rollback",
|
|
11
11
|
"robinhood-chain",
|
|
12
|
-
"elizaos"
|
|
12
|
+
"elizaos",
|
|
13
|
+
"langchain",
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol"
|
|
13
16
|
],
|
|
14
17
|
"homepage": "https://upoch.tech/docs",
|
|
15
18
|
"license": "MIT",
|