@kudo-dev/sdk 0.0.0-stage → 0.2.0
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/.env.example +86 -0
- package/AGENT.md +201 -0
- package/README.md +123 -2
- package/dist/agent/index.d.ts +94 -0
- package/dist/agent/index.js +867 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/index.cjs +2562 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1823 -0
- package/dist/index.d.ts +1823 -0
- package/dist/index.js +2520 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -4
- package/prompts/common.md +128 -0
- package/prompts/settle-due.txt +92 -0
- package/prompts/take-work.txt +43 -0
- package/skills/chain-forensics/SKILL.md +127 -0
- package/skills/kudo/SKILL.md +370 -0
- package/skills/kudo/sponsored-send.mjs +218 -0
- package/skills/kudo-runtime/SKILL.md +275 -0
- package/skills/kudo-validation/SKILL.md +239 -0
- package/skills/twitter/SKILL.md +127 -0
package/.env.example
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Environment for an agent started with startAgent (@kudo-dev/sdk/agent).
|
|
2
|
+
# Copy to your agent's folder as .env and fill in. Never commit it.
|
|
3
|
+
#
|
|
4
|
+
# Who the agent is (its system prompt, extra skills and jobs) is code, passed
|
|
5
|
+
# to startAgent. Only secrets and per-deployment values live here.
|
|
6
|
+
|
|
7
|
+
ANTHROPIC_API_KEY=
|
|
8
|
+
|
|
9
|
+
# This agent's signing key. In this PoC it sits in the environment, which
|
|
10
|
+
# means anything that can run a shell in this container can read it —
|
|
11
|
+
# including the agent itself. Covenant asks are written by counterparties, so
|
|
12
|
+
# treat on-chain text as untrusted input.
|
|
13
|
+
PRIVATE_KEY=
|
|
14
|
+
|
|
15
|
+
# The name this agent registers on-chain. Its ERC-8004 id needs no setting:
|
|
16
|
+
# at startup the runtime finds the identity this wallet registered, and a new
|
|
17
|
+
# agent registers one at its first run.
|
|
18
|
+
AGENT_NAME=
|
|
19
|
+
# Where this agent is reachable, registered as ERC-8004 `endpoint` metadata.
|
|
20
|
+
AGENT_ENDPOINT=
|
|
21
|
+
|
|
22
|
+
# --- Robinhood Chain mainnet (chainId 4663) ---
|
|
23
|
+
# The live deployment, and the only one the skills support. USDG here is real
|
|
24
|
+
# money, so start with a throwaway wallet and small amounts.
|
|
25
|
+
RPC_URL=https://rpc.mainnet.chain.robinhood.com
|
|
26
|
+
KUDO=0x1930D0FEaB14b024353bF22adE03Db989aD640C7
|
|
27
|
+
IDENTITY_REGISTRY=0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
|
|
28
|
+
|
|
29
|
+
# --- Gas sponsorship ---
|
|
30
|
+
# The relay that pays this agent's gas while sponsorship runs. Every write tries
|
|
31
|
+
# it first and falls back to a plain transaction (paid in ETH) only when nothing
|
|
32
|
+
# was sent. Empty means Kudo's relay,
|
|
33
|
+
# https://app.kudo.archi/api/sponsor; "off" means always pay your own gas.
|
|
34
|
+
KUDO_SPONSOR_URL=
|
|
35
|
+
|
|
36
|
+
# --- Settlement token ---
|
|
37
|
+
# USDG is an ERC-20 with six decimals, so 1 USDG is 1000000 base units (5 USDG
|
|
38
|
+
# is 5000000; never cast to-wei). ETH is for gas only and is never what a
|
|
39
|
+
# promise is denominated in.
|
|
40
|
+
USDG=0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168
|
|
41
|
+
|
|
42
|
+
# --- Schedule overrides (optional) ---
|
|
43
|
+
# The agent's own `jobs` (passed to startAgent) apply unless one of these is set.
|
|
44
|
+
# JOBS <job>:<seconds>, comma separated, minimum ten seconds.
|
|
45
|
+
# A job the agent defines keeps its own prompt; any
|
|
46
|
+
# other name runs the bundled prompt of that name.
|
|
47
|
+
# TICK_INTERVAL_SECONDS 0 runs no jobs: something else owns this agent's work
|
|
48
|
+
# Jobs share one key, so only one run happens at a time — a run that comes due
|
|
49
|
+
# while another is going waits rather than colliding on the nonce.
|
|
50
|
+
JOBS=
|
|
51
|
+
TICK_INTERVAL_SECONDS=
|
|
52
|
+
|
|
53
|
+
# --- Logging ---
|
|
54
|
+
# Every run logs what started it, each command the agent runs, the output of
|
|
55
|
+
# any that fails, and how it ended (turns, cost, errors). debug also logs every
|
|
56
|
+
# command's output and what the agent said. Secrets are redacted either way.
|
|
57
|
+
LOG_LEVEL=info
|
|
58
|
+
|
|
59
|
+
# --- Phala TEE ---
|
|
60
|
+
# The app id is read at startup from the dstack socket, so leave this blank on
|
|
61
|
+
# Phala. Set it by hand only to exercise the code path locally, where it is
|
|
62
|
+
# reported as unattested and never written on-chain.
|
|
63
|
+
TEE_ID=
|
|
64
|
+
|
|
65
|
+
# --- X / Twitter ---
|
|
66
|
+
# All four are required. If any is missing the request goes out unsigned and X
|
|
67
|
+
# answers 403 "Authenticating with Unknown", which reads like a permissions
|
|
68
|
+
# problem but is not.
|
|
69
|
+
#
|
|
70
|
+
# The access token must be read-write. Setting the app to "Read and write" in
|
|
71
|
+
# the developer portal is not enough — regenerate the access token and secret
|
|
72
|
+
# afterwards, or the old token keeps its read-only permission.
|
|
73
|
+
TWITTER_API_KEY=
|
|
74
|
+
TWITTER_API_SECRET_KEY=
|
|
75
|
+
TWITTER_ACCESS_TOKEN=
|
|
76
|
+
TWITTER_ACCESS_TOKEN_SECRET=
|
|
77
|
+
TWITTER_HANDLE=
|
|
78
|
+
|
|
79
|
+
# --- Older deployments: not supported ---
|
|
80
|
+
# These are earlier Kudo contract versions with a different status enum. The
|
|
81
|
+
# skills and prompts no longer describe them; do not point KUDO at either.
|
|
82
|
+
# mainnet legacy: KUDO=0xc269c6176f3418A84FCdA5911723B0d358971351
|
|
83
|
+
# testnet (46630): RPC_URL=https://rpc.testnet.chain.robinhood.com
|
|
84
|
+
# KUDO=0x7021279517a7b301b8d10eD9c3b9F73C101146f1
|
|
85
|
+
# IDENTITY_REGISTRY=0x8004A818BFB912233c491871b3d84c89A494BD9e
|
|
86
|
+
# USDG=0xc44DB72f7d3185b6AAA7907434592EB7689D0100 (mock)
|
package/AGENT.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Run your own Kudo agent
|
|
2
|
+
|
|
3
|
+
`@kudo-dev/sdk/agent` is everything needed to run an agent that makes and keeps
|
|
4
|
+
Kudo covenants on its own. It runs on the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk)
|
|
5
|
+
and works the way a person would at a shell: `cast` for the chain, `node` to
|
|
6
|
+
sign X API requests. There are **no custom tools**; what it knows lives in its
|
|
7
|
+
skills and its system prompt.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @kudo-dev/sdk @anthropic-ai/claude-agent-sdk tsx
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You also need [`cast`](https://getfoundry.sh) on the `PATH`.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// index.ts
|
|
17
|
+
import { startAgent } from "@kudo-dev/sdk/agent";
|
|
18
|
+
|
|
19
|
+
await startAgent({
|
|
20
|
+
systemPrompt: new URL("./system.md", import.meta.url), // who your agent is
|
|
21
|
+
skills: ["chain-forensics"], // added to kudo, kudo-runtime, twitter
|
|
22
|
+
jobs: {
|
|
23
|
+
"settle-due": { interval: 300 }, // a bundled prompt, every 300s
|
|
24
|
+
"watch-bounties": { // a job of your own
|
|
25
|
+
interval: 600,
|
|
26
|
+
prompt: new URL("./watch-bounties.txt", import.meta.url),
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cp node_modules/@kudo-dev/sdk/.env.example .env # fill in ANTHROPIC_API_KEY and PRIVATE_KEY
|
|
34
|
+
npx tsx --env-file=.env index.ts
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Kudo's own agents are exactly this: see [`agents/shinichi`](../../agents/shinichi)
|
|
38
|
+
and [`agents/megure`](../../agents/megure) in this repository.
|
|
39
|
+
|
|
40
|
+
## Options
|
|
41
|
+
|
|
42
|
+
| Option | |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `systemPrompt` | who the agent is: text, a path, or a `file:` URL. It is appended to [`prompts/common.md`](prompts/common.md), which every Kudo agent reads first — the covenant model, the honesty rules, how to work at the shell |
|
|
45
|
+
| `skills` | skills beyond the defaults: a name from [`skills/`](skills) or a path / `file:` URL to a skill folder of your own |
|
|
46
|
+
| `jobs` | scheduled jobs by name, each `{ interval, prompt? }`: seconds between runs (at least 10) and the task prompt as text, a path or a `file:` URL. Without `prompt`, a job runs the bundled prompt of its name. Default `{ "settle-due": { interval: 300 } }` |
|
|
47
|
+
| `port` | HTTP port. Default `PORT`, else 3000 |
|
|
48
|
+
| `model` | Claude model id. Default `claude-opus-5` |
|
|
49
|
+
| `cwd` | the agent's shell working directory. Default: the system prompt file's folder |
|
|
50
|
+
|
|
51
|
+
The chain, the key and the API key come from the environment: see
|
|
52
|
+
[`.env.example`](.env.example). It ships set to Robinhood Chain **mainnet**,
|
|
53
|
+
where USDG is real money, so start with a throwaway wallet holding small
|
|
54
|
+
amounts. `JOBS` and `TICK_INTERVAL_SECONDS=0` override the schedule without a
|
|
55
|
+
code change. While Kudo sponsors gas, the agent's writes, payments included,
|
|
56
|
+
cost it nothing: it uses Kudo's relay unless `KUDO_SPONSOR_URL` names another
|
|
57
|
+
(`off` turns it off). When the relay refuses or sponsorship has ended, they
|
|
58
|
+
fall back to ordinary transactions paid in ETH.
|
|
59
|
+
|
|
60
|
+
The agent's ERC-8004 id needs no configuration. At startup the runtime reads
|
|
61
|
+
the Identity Registry's `Registered` events for the agent's wallet and uses
|
|
62
|
+
the identity it finds. A new agent has none, so it registers at its first run
|
|
63
|
+
and the runtime uses that id from then on.
|
|
64
|
+
|
|
65
|
+
## Skills
|
|
66
|
+
|
|
67
|
+
Every agent gets these three:
|
|
68
|
+
|
|
69
|
+
| Skill | |
|
|
70
|
+
|---|---|
|
|
71
|
+
| [`kudo`](skills/kudo/SKILL.md) | the public Kudo skill: every contract call, the covenant stages, identity. The same file any outside agent reads |
|
|
72
|
+
| [`kudo-runtime`](skills/kudo-runtime/SKILL.md) | this runtime's environment, the preflight, USDG payments and verification, tweet legs, revert selectors, honesty rules |
|
|
73
|
+
| [`twitter`](skills/twitter/SKILL.md) | posting to X: OAuth 1.0a signing with no dependencies, and what each 403 means |
|
|
74
|
+
|
|
75
|
+
Add others by name:
|
|
76
|
+
|
|
77
|
+
| Skill | For |
|
|
78
|
+
|---|---|
|
|
79
|
+
| [`chain-forensics`](skills/chain-forensics/SKILL.md) | following a trail: reading a contract's events without its ABI, tracing ERC-20 transfers, finding who funded a wallet |
|
|
80
|
+
| [`kudo-validation`](skills/kudo-validation/SKILL.md) | the Evaluator only: accepting asks (status `1`) and closing covenants as kept or broken |
|
|
81
|
+
|
|
82
|
+
An agent is given exactly its skills and no others: the runtime loads them as
|
|
83
|
+
one plugin built for that agent, so `kudo-validation` is never even visible to
|
|
84
|
+
an agent that did not ask for it.
|
|
85
|
+
|
|
86
|
+
## Task prompts
|
|
87
|
+
|
|
88
|
+
A job runs one task prompt on its interval: the `prompt` you gave it, or else
|
|
89
|
+
one of these, bundled, matched by the job's name:
|
|
90
|
+
|
|
91
|
+
| Prompt | Does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| [`take-work`](prompts/take-work.txt) | find one unclaimed open ask, perform it, claim it |
|
|
94
|
+
| [`settle-due`](prompts/settle-due.txt) | keep the promises this agent owes (covenants at status `1`) |
|
|
95
|
+
|
|
96
|
+
A job with no `prompt` and no bundled prompt of its name stops the agent at
|
|
97
|
+
startup. Jobs keep separate schedules but
|
|
98
|
+
share one key, so only one run happens at a time: a run that comes due while
|
|
99
|
+
another is going waits rather than colliding on the nonce. A failed round is
|
|
100
|
+
logged and skipped rather than stopping the schedule, and each job also runs
|
|
101
|
+
once at startup.
|
|
102
|
+
|
|
103
|
+
## Logs
|
|
104
|
+
|
|
105
|
+
Every line names the run it belongs to: the job's name (or `message`) and a
|
|
106
|
+
short id, so interleaved runs can be told apart.
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
10:30:06 [settle-due 3fa1c2] started: Settle every covenant this agent owes…
|
|
110
|
+
10:30:09 [settle-due 3fa1c2] skill kudo:kudo-runtime
|
|
111
|
+
10:30:12 [settle-due 3fa1c2] $ cast call $KUDO 'getCovenantsDetails()(…)' --rpc-url $RPC_URL
|
|
112
|
+
10:30:15 [settle-due 3fa1c2] failed: $ node "$KUDO_SPONSORED_SEND" $USDG "$(cast calldata …)"
|
|
113
|
+
sponsored-send: not sent (403 not_allowed: …) — safe to fall back to cast send
|
|
114
|
+
10:30:41 [settle-due 3fa1c2] done (35.2s, 9 turns, $0.0912)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
By default a run logs:
|
|
118
|
+
- what started it;
|
|
119
|
+
- each command the agent runs and each skill it loads;
|
|
120
|
+
- the output of any command that fails;
|
|
121
|
+
- how it ended: time, turns and cost, or the SDK's error text;
|
|
122
|
+
- any tool it was denied;
|
|
123
|
+
- Claude Code's stderr.
|
|
124
|
+
|
|
125
|
+
`LOG_LEVEL=debug` adds every command's output and what the agent said.
|
|
126
|
+
Secrets (`PRIVATE_KEY`, `ANTHROPIC_API_KEY`, the X credentials) are replaced
|
|
127
|
+
by their names in every line.
|
|
128
|
+
|
|
129
|
+
## Endpoints
|
|
130
|
+
|
|
131
|
+
| Endpoint | Does |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `POST /message` | `{"message": "..."}` → `{"response": "..."}` — one agent run |
|
|
134
|
+
| `GET /identity` | name, `agentId`, `teeId`, and where to verify the TEE |
|
|
135
|
+
| `GET /health` | liveness |
|
|
136
|
+
|
|
137
|
+
A run can take minutes, so `/message` disables the socket timeout. There is no
|
|
138
|
+
queue: a second request starts a second run.
|
|
139
|
+
|
|
140
|
+
## Docker and Phala
|
|
141
|
+
|
|
142
|
+
One image per agent, built from the repository root:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
docker build -f packages/sdk/Dockerfile --build-arg AGENT=agents/shinichi -t kudo-shinichi .
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`cast` is baked in from `ghcr.io/foundry-rs/foundry`, pinned by digest, not
|
|
149
|
+
installed at runtime: an agent that bootstraps its own toolchain fails slowly
|
|
150
|
+
and mid-task, and in a TEE it weakens what attestation tells you about the
|
|
151
|
+
image. The build runs `cast --version` so a broken copy fails the build rather
|
|
152
|
+
than the first covenant.
|
|
153
|
+
|
|
154
|
+
[`docker-compose.yml`](docker-compose.yml) is the deployment template. It is
|
|
155
|
+
the file Phala hashes and publishes, so it pins the image **by digest** rather
|
|
156
|
+
than by tag — a tag can be repointed at different bytes later, which would make
|
|
157
|
+
the attestation prove nothing about the code. Push your build and put its
|
|
158
|
+
digest there before deploying. Secrets are `${VARS}` declared in the Phala
|
|
159
|
+
dashboard, never values in the file.
|
|
160
|
+
|
|
161
|
+
## Verifying what is running
|
|
162
|
+
|
|
163
|
+
In a Phala CVM the agent reads its app id from `/var/run/dstack.sock` at
|
|
164
|
+
startup, registers it as `teeId` metadata on its ERC-8004 identity, and serves
|
|
165
|
+
it from `GET /identity`. The app id is the last path segment of the agent's
|
|
166
|
+
Trust Center report, `https://trust.phala.com/app/<teeId>`. Anyone holding a
|
|
167
|
+
covenant against the agent can open it and see — checked independently of the
|
|
168
|
+
agent — that the code runs on genuine Intel TDX hardware, on an unmodified
|
|
169
|
+
dstack OS, from the compose file and image digest the report names.
|
|
170
|
+
|
|
171
|
+
Outside a TEE there is no app id. The agent still runs; `teeId` and
|
|
172
|
+
`attestation` come back `null`, and it registers its name alone.
|
|
173
|
+
|
|
174
|
+
## What the agent can and cannot do
|
|
175
|
+
|
|
176
|
+
Allowed: `Bash` and `Skill`, and nothing else. `allowedTools` alone would not
|
|
177
|
+
achieve that — it is an auto-approve list, not a filter — so every other
|
|
178
|
+
built-in tool, and any connector on the API key's account, is denied by name.
|
|
179
|
+
`permissionMode: "dontAsk"` then denies anything not pre-approved, because no
|
|
180
|
+
human is at the keyboard. No project or user settings are read, so nothing on
|
|
181
|
+
the host machine changes what the agent knows.
|
|
182
|
+
|
|
183
|
+
It mints only under an ERC-8004 identity its own wallet owns — the contract
|
|
184
|
+
refuses any other `agentId`. It cannot close covenants unless its wallet holds
|
|
185
|
+
`EVALUATOR_CONTRACT_ROLE`.
|
|
186
|
+
|
|
187
|
+
## Known limits
|
|
188
|
+
|
|
189
|
+
**The key is in the environment and the agent has a shell**, so the agent can
|
|
190
|
+
read its own key. Covenant asks are written by counterparties and the agent
|
|
191
|
+
reads them from chain, which makes on-chain text an untrusted input path. Fine
|
|
192
|
+
for a proof of concept; before anything real, either move writes behind typed
|
|
193
|
+
tools or put the key behind a signer that enforces amount and recipient policy.
|
|
194
|
+
|
|
195
|
+
**Shell output is not schema-validated.** A typed tool would compute a
|
|
196
|
+
repayment amount in code; here the model composes the whole `cast send`. The
|
|
197
|
+
skills compensate by stating amounts with their token and keeping one numeral
|
|
198
|
+
per promise, but that is a convention, not an enforcement.
|
|
199
|
+
|
|
200
|
+
**Each scheduled round costs a model call.** The loop wakes on a timer and asks
|
|
201
|
+
the agent to look, rather than waking on a chain event.
|
package/README.md
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kudo-dev/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript library for [Kudo](https://app.kudo.archi) covenants on
|
|
4
|
+
Robinhood Chain: register an ERC-8004 agent, mint a covenant, meet an ask,
|
|
5
|
+
link proof, and read covenants and agent records.
|
|
6
|
+
|
|
7
|
+
It talks to the Kudo contract and the ERC-8004 registries only. It never
|
|
8
|
+
moves tokens: when a promise is a payment, pay from your own wallet and link
|
|
9
|
+
the transaction hash with `linkProof`. The Kudo MCP server and the ElizaOS
|
|
10
|
+
plugin are built on it.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @kudo-dev/sdk viem
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Want an agent that works covenants on its own?** `@kudo-dev/sdk/agent` has
|
|
17
|
+
`startAgent`: give it a system prompt and it runs a Kudo agent on the Claude
|
|
18
|
+
Agent SDK, with the Kudo skills built in. See [AGENT.md](AGENT.md). The library
|
|
19
|
+
below never loads it.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { startAgent } from "@kudo-dev/sdk/agent";
|
|
23
|
+
|
|
24
|
+
await startAgent({ systemPrompt: new URL("./system.md", import.meta.url) });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Read
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createKudo } from "@kudo-dev/sdk";
|
|
31
|
+
|
|
32
|
+
const kudo = createKudo(); // Robinhood Chain mainnet, public RPC
|
|
33
|
+
|
|
34
|
+
const asks = await kudo.listOpenAsks({ limit: 10 });
|
|
35
|
+
const covenant = await kudo.getCovenant(109);
|
|
36
|
+
const record = await kudo.agentRecord(20380); // kept vs broken
|
|
37
|
+
const [myId] = await kudo.findAgentIds("0xYourWallet");
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Write with your own wallet
|
|
41
|
+
|
|
42
|
+
The SDK never stores or asks for a key. Pass any viem wallet client:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { createWalletClient, http } from "viem";
|
|
46
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
47
|
+
import { createKudo, robinhood } from "@kudo-dev/sdk";
|
|
48
|
+
|
|
49
|
+
const walletClient = createWalletClient({
|
|
50
|
+
account: privateKeyToAccount(process.env.KUDO_PRIVATE_KEY as `0x${string}`),
|
|
51
|
+
chain: robinhood,
|
|
52
|
+
transport: http(),
|
|
53
|
+
});
|
|
54
|
+
const kudo = createKudo({ walletClient });
|
|
55
|
+
|
|
56
|
+
const { agentId } = await kudo.registerAgent({ name: "Ada" }); // once per wallet
|
|
57
|
+
const { covenantId, url } = await kudo.mintCovenant({
|
|
58
|
+
agentId,
|
|
59
|
+
type: "INVOICE",
|
|
60
|
+
ask: "Write a 300-word explainer of ERC-8004 and publish it on IPFS.",
|
|
61
|
+
promise: "I'll pay 5 USDG within one hour of the link going up.",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// later, once someone has met the ask:
|
|
65
|
+
const c = await kudo.getCovenant(covenantId); // c.promiseDetail → "pay: 0x…"
|
|
66
|
+
// pay what the promise says from your own wallet (USDG has 6 decimals), then:
|
|
67
|
+
await kudo.linkProof(covenantId, paymentTxHash); // refuses until the Evaluator accepts the ask (IN_PROGRESS), or twice
|
|
68
|
+
await kudo.watchSettlement(covenantId); // resolves when closed
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every write simulates first and throws a `KudoError` that names the revert
|
|
72
|
+
(`NotAuthorizedForAgent`, `AccessForbidden`, …) and says what to do. Writes also
|
|
73
|
+
refuse the common mistakes before they cost gas: minting under an id you do not
|
|
74
|
+
own, meeting an ask someone already met, linking proof on someone else's
|
|
75
|
+
covenant, or registering a second identity.
|
|
76
|
+
|
|
77
|
+
## Or build unsigned transactions
|
|
78
|
+
|
|
79
|
+
For a hosted service, a browser wallet, a smart account or a remote signer:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const tx = kudo.buildMintCovenant({ agentId, type: "LOAN", ask, promise });
|
|
83
|
+
// { to, data, value, chainId, description }
|
|
84
|
+
await kudo.simulate(tx, "0xSigner"); // optional dry-run as the signer
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`buildMeetAsk`, `buildLinkProof`, `buildRegisterAgent` and the
|
|
88
|
+
generic `buildTx` work the same way.
|
|
89
|
+
|
|
90
|
+
## Covenant stages
|
|
91
|
+
|
|
92
|
+
`status` is the contract's field and only the Evaluator moves it: to
|
|
93
|
+
`IN_PROGRESS` when it has checked and accepted the ask's evidence, and to
|
|
94
|
+
`COMPLETED` or `FAILED` when it closes the covenant. Use `stage`:
|
|
95
|
+
|
|
96
|
+
| stage | meaning |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `ask-open` | nobody has met the ask; anyone can take it |
|
|
99
|
+
| `ask-claimed` | someone claimed the ask; the Evaluator has not accepted it, so nothing is owed yet |
|
|
100
|
+
| `promise-due` | the Evaluator accepted the claim (`IN_PROGRESS`); the minter owes the promise now |
|
|
101
|
+
| `awaiting-evaluation` | both legs have proof |
|
|
102
|
+
| `completed` / `failed` | closed by the Evaluator |
|
|
103
|
+
|
|
104
|
+
## Claims are not protected by the contract
|
|
105
|
+
|
|
106
|
+
Anyone can call `setAskSettlementData` on a covenant again and replace an
|
|
107
|
+
earlier claim; the contract's ask cooldown (`getAskCooldown()`) does not
|
|
108
|
+
prevent it. `meetAsk` therefore refuses an ask that is already met, and after
|
|
109
|
+
mining re-reads the covenant and throws `ALREADY_MET` if the proof on chain is
|
|
110
|
+
not yours. As the promiser, re-read the covenant right before you pay, and pay
|
|
111
|
+
the `promiseDetail` it shows then.
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
`createKudo({ rpcUrl, overrides })`. Robinhood Chain mainnet is the only
|
|
116
|
+
network: older Kudo deployments use a different status enum and are not
|
|
117
|
+
supported.
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm test # offline
|
|
123
|
+
KUDO_LIVE=1 npm test # plus read-only checks against mainnet
|
|
124
|
+
```
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
declare const DEFAULT_SKILLS: string[];
|
|
2
|
+
/** A bundled skill's name, or a path (or file: URL) to a skill folder with a SKILL.md. */
|
|
3
|
+
type SkillRef = string | URL;
|
|
4
|
+
/** Skills this package ships. */
|
|
5
|
+
declare function bundledSkills(): Promise<string[]>;
|
|
6
|
+
interface SkillPlugin {
|
|
7
|
+
/** The plugin directory to pass to the Agent SDK. */
|
|
8
|
+
path: string;
|
|
9
|
+
/** The skills' names as the SDK lists them, `kudo:<skill>`. */
|
|
10
|
+
names: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** What one agent runs with, fixed at startup. */
|
|
14
|
+
interface AgentSession {
|
|
15
|
+
/** common.md followed by the agent's own system prompt. */
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
/** The plugin holding exactly this agent's skills. */
|
|
18
|
+
skills: SkillPlugin;
|
|
19
|
+
/** Working directory for the shell: the agent's own folder. */
|
|
20
|
+
cwd: string;
|
|
21
|
+
/** Claude model id. */
|
|
22
|
+
model: string;
|
|
23
|
+
}
|
|
24
|
+
declare class AgentRunError extends Error {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Runs one task to completion and returns everything the agent said.
|
|
28
|
+
* `label` names what started it (a job's name, or `message`) in the log.
|
|
29
|
+
*
|
|
30
|
+
* Throws AgentRunError if the run did not finish — callers decide whether that
|
|
31
|
+
* is a 500 or a logged line.
|
|
32
|
+
*/
|
|
33
|
+
declare function runAgent(prompt: string, session: AgentSession, label?: string): Promise<string>;
|
|
34
|
+
|
|
35
|
+
/** Text, or a path / file: URL to read it from. */
|
|
36
|
+
type TextSource = string | URL;
|
|
37
|
+
/**
|
|
38
|
+
* The agent's standing instructions: the shared base, then its character.
|
|
39
|
+
*
|
|
40
|
+
* Order matters. The character comes last so that where the two touch - voice,
|
|
41
|
+
* register, what it will and will not claim - the specific one is what the
|
|
42
|
+
* model read most recently.
|
|
43
|
+
*/
|
|
44
|
+
declare function loadSystemPrompt(character: TextSource): Promise<string>;
|
|
45
|
+
/** Task prompts this package ships. */
|
|
46
|
+
declare function bundledPrompts(): Promise<string[]>;
|
|
47
|
+
/** A job's task prompt: its own if it has one, else this package's prompt of that name. */
|
|
48
|
+
declare function loadPrompt(name: string, own?: TextSource): Promise<string>;
|
|
49
|
+
|
|
50
|
+
/** One scheduled job, as an agent passes it to startAgent. */
|
|
51
|
+
interface AgentJob {
|
|
52
|
+
/** Seconds between runs, at least ten. */
|
|
53
|
+
interval: number;
|
|
54
|
+
/** The task prompt: text, a path or a file: URL. Default: the bundled prompt of the job's name. */
|
|
55
|
+
prompt?: TextSource;
|
|
56
|
+
}
|
|
57
|
+
interface JobSpec {
|
|
58
|
+
name: string;
|
|
59
|
+
intervalSeconds: number;
|
|
60
|
+
/** The job's own prompt; undefined means the bundled one named `name`. */
|
|
61
|
+
prompt?: TextSource;
|
|
62
|
+
}
|
|
63
|
+
/** The jobs to run: JOBS if set, none if TICK_INTERVAL_SECONDS is 0, else the agent's own. */
|
|
64
|
+
declare function resolveJobs(own: Record<string, AgentJob> | undefined, env?: NodeJS.ProcessEnv): JobSpec[];
|
|
65
|
+
|
|
66
|
+
interface AgentOptions {
|
|
67
|
+
/** Who this agent is: the text, or a path / file: URL to it (e.g. `new URL("./system.md", import.meta.url)`). */
|
|
68
|
+
systemPrompt: TextSource;
|
|
69
|
+
/**
|
|
70
|
+
* Skills beyond the defaults (`kudo`, `kudo-runtime`, `twitter`): the name of
|
|
71
|
+
* one this package ships (`chain-forensics`, `kudo-validation`) or a path /
|
|
72
|
+
* file: URL to a skill folder of your own.
|
|
73
|
+
*/
|
|
74
|
+
skills?: SkillRef[];
|
|
75
|
+
/**
|
|
76
|
+
* Scheduled jobs by name, each `{ interval, prompt? }`: seconds between runs
|
|
77
|
+
* and the task prompt (text, a path or a file: URL). A job without a prompt
|
|
78
|
+
* runs the bundled one of its name (`take-work`, `settle-due`). Default
|
|
79
|
+
* `{ "settle-due": { interval: 300 } }`; JOBS overrides the schedule.
|
|
80
|
+
*/
|
|
81
|
+
jobs?: Record<string, AgentJob>;
|
|
82
|
+
/** HTTP port. Default PORT, else 3000. */
|
|
83
|
+
port?: number;
|
|
84
|
+
/** Claude model id. Default `claude-opus-5`. */
|
|
85
|
+
model?: string;
|
|
86
|
+
/** Working directory for the agent's shell. Default: the system prompt file's folder, else the current one. */
|
|
87
|
+
cwd?: string;
|
|
88
|
+
}
|
|
89
|
+
/** The fixed parts of a run: system prompt, skills, working directory, model. */
|
|
90
|
+
declare function createSession(options: AgentOptions): Promise<AgentSession>;
|
|
91
|
+
/** Starts the agent: the HTTP endpoint and its scheduled jobs. Resolves once both are running. */
|
|
92
|
+
declare function startAgent(options: AgentOptions): Promise<void>;
|
|
93
|
+
|
|
94
|
+
export { type AgentJob, type AgentOptions, AgentRunError, type AgentSession, DEFAULT_SKILLS, type JobSpec, type SkillRef, type TextSource, bundledPrompts, bundledSkills, createSession, loadPrompt, loadSystemPrompt, resolveJobs, runAgent, startAgent };
|