@kudo-dev/sdk 0.1.0 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/start.ts","../../src/agent/agent.ts","../../src/agent/log.ts","../../src/agent/agent-id.ts","../../src/abi/registries.ts","../../src/networks.ts","../../src/agent/config.ts","../../src/agent/jobs.ts","../../src/agent/prompts.ts","../../src/agent/paths.ts","../../src/agent/server/index.ts","../../src/agent/server/http.ts","../../src/agent/server/handlers.ts","../../src/agent/skills.ts","../../src/errors.ts","../../src/dstack.ts","../../src/agent/tee.ts"],"sourcesContent":["/**\n * startAgent: everything needed to run a Kudo agent.\n *\n * Starts the HTTP endpoint and the scheduled jobs, and stops both cleanly when\n * asked. The agent supplies who it is (its system prompt) and anything beyond\n * the defaults (extra skills, its own task prompts, its job schedule); the\n * chain, the key and the API key come from the environment (see .env.example).\n *\n * SHUTDOWN_GRACE_SECONDS how long a run may finish in on shutdown (default 120)\n */\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { runAgent, type AgentSession } from \"./agent.js\";\nimport { prepareAgentIdFile, resolveAgentId, syncAgentId } from \"./agent-id.js\";\nimport { requireEnvironment, warnIfTwitterIncomplete } from \"./config.js\";\nimport { exclusive, resolveJobs, startJobs, whenRunsIdle, type AgentJob, type JobSpec } from \"./jobs.js\";\nimport { loadPrompt, loadSystemPrompt, type TextSource } from \"./prompts.js\";\nimport { startServer } from \"./server/index.js\";\nimport { SKILLS_DIR } from \"./paths.js\";\nimport { assembleSkills, type SkillRef } from \"./skills.js\";\nimport { resolveTeeIdentity } from \"./tee.js\";\n\nexport interface AgentOptions {\n /** Who this agent is: the text, or a path / file: URL to it (e.g. `new URL(\"./system.md\", import.meta.url)`). */\n systemPrompt: TextSource;\n /**\n * Skills beyond the defaults (`kudo`, `kudo-runtime`, `twitter`): the name of\n * one this package ships (`chain-forensics`, `kudo-validation`) or a path /\n * file: URL to a skill folder of your own.\n */\n skills?: SkillRef[];\n /**\n * Scheduled jobs by name, each `{ interval, prompt? }`: seconds between runs\n * and the task prompt (text, a path or a file: URL). A job without a prompt\n * runs the bundled one of its name (`take-work`, `settle-due`). Default\n * `{ \"settle-due\": { interval: 300 } }`; JOBS overrides the schedule.\n */\n jobs?: Record<string, AgentJob>;\n /** HTTP port. Default PORT, else 3000. */\n port?: number;\n /** Claude model id. Default `claude-opus-5`. */\n model?: string;\n /** Working directory for the agent's shell. Default: the system prompt file's folder, else the current one. */\n cwd?: string;\n}\n\n/**\n * Must stay below the orchestrator's stop grace period, or the process is\n * SIGKILLed first and the graceful path never runs.\n */\nconst SHUTDOWN_GRACE_MS = Number(process.env.SHUTDOWN_GRACE_SECONDS?.trim() || 120) * 1000;\n\n/**\n * Resolves the Phala app id and puts it in the environment, where the agent\n * picks it up and registers it as `teeId` metadata on its ERC-8004 identity.\n */\nasync function announceTee(): Promise<void> {\n const tee = await resolveTeeIdentity();\n\n if (!tee) {\n console.log(\"no TEE app id — running without attestation\");\n return;\n }\n\n process.env.TEE_ID = tee.teeId;\n process.env.TEE_TRUST_URL = tee.trustCenterUrl;\n process.env.TEE_ID_SOURCE = tee.source;\n\n if (tee.source === \"dstack\") {\n console.log(`attestation: ${tee.trustCenterUrl}`);\n return;\n }\n\n // A mocked id resolves to a real report for somebody else's app. Say so, or\n // a local run reads exactly like an attested one.\n console.log(`TEE_ID set by hand: ${tee.trustCenterUrl}`);\n console.log(\" that report is NOT this process — local stand-in, attests nothing\");\n}\n\nfunction defaultCwd(source: TextSource): string {\n if (source instanceof URL) return dirname(fileURLToPath(source));\n // A one-line string naming an existing file is a path (as prompts.ts reads it).\n if (!source.includes(\"\\n\") && existsSync(source)) return dirname(resolve(source));\n return process.cwd();\n}\n\n/** The fixed parts of a run: system prompt, skills, working directory, model. */\nexport async function createSession(options: AgentOptions): Promise<AgentSession> {\n const skills = await assembleSkills(options.skills);\n return {\n systemPrompt: await loadSystemPrompt(options.systemPrompt),\n skills,\n cwd: resolve(options.cwd ?? defaultCwd(options.systemPrompt)),\n model: options.model ?? \"claude-opus-5\",\n };\n}\n\n/** Starts the agent: the HTTP endpoint and its scheduled jobs. Resolves once both are running. */\nexport async function startAgent(options: AgentOptions): Promise<void> {\n requireEnvironment();\n warnIfTwitterIncomplete();\n // AGENT_ID comes from the registry, never from the environment (agent-id.ts).\n await resolveAgentId();\n await announceTee();\n // The kudo skill's sponsored-send script, which every write tries first.\n process.env.KUDO_SPONSORED_SEND = join(SKILLS_DIR, \"kudo\", \"sponsored-send.mjs\");\n\n const session = await createSession(options);\n console.log(`skills: ${session.skills.names.join(\", \")}`);\n\n const jobs = resolveJobs(options.jobs);\n // Fail now, not at the first tick, if a job has no prompt to run.\n for (const job of jobs) await loadPrompt(job.name, job.prompt);\n\n // Where a new agent writes its id after registering; every run starts from it.\n await prepareAgentIdFile();\n const beforeRun = async () => {\n const recorded = await syncAgentId();\n if (recorded) console.log(`AGENT_ID ${recorded}, as the agent recorded it after registering`);\n };\n\n // Through the same lock as the jobs: a message run signs from the same key.\n const run = (prompt: string) =>\n exclusive(async () => {\n await beforeRun();\n return runAgent(prompt, session, \"message\");\n });\n // Read on every run, so an edited prompt file takes effect at the next tick.\n const runJob = async (job: JobSpec) => {\n await beforeRun();\n return runAgent(await loadPrompt(job.name, job.prompt), session, job.name);\n };\n\n const server = startServer(run, options.port);\n const scheduled = startJobs(jobs, runJob);\n\n let shuttingDown = false;\n\n /**\n * Stop accepting work, then let any run in flight finish rather than being\n * killed between a payment and its attestation — but not forever.\n */\n function shutdown(): void {\n if (shuttingDown) {\n console.log(\"second signal — exiting now\");\n process.exit(130);\n }\n shuttingDown = true;\n\n console.log(`shutting down (up to ${SHUTDOWN_GRACE_MS / 1000}s for the current run)`);\n server.close();\n scheduled.stop();\n\n // .unref() so a pending timer cannot keep the process alive on its own.\n const deadline = new Promise<\"timeout\">((resolveTimeout) =>\n setTimeout(() => resolveTimeout(\"timeout\"), SHUTDOWN_GRACE_MS).unref(),\n );\n\n // Jobs, and any /message run in flight or already queued behind the lock.\n const idle = Promise.all([scheduled.whenIdle(), whenRunsIdle()]);\n void Promise.race([idle, deadline]).then((outcome) => {\n if (outcome === \"timeout\") console.error(\"run did not finish in time — exiting anyway\");\n process.exit(0);\n });\n }\n\n process.on(\"SIGTERM\", shutdown); // orchestrator stopping the container\n process.on(\"SIGINT\", shutdown); // Ctrl-C\n}\n","/**\n * The agent.\n *\n * One function: give it a prompt, get its answer back. There are no custom\n * tools — it works the way a person would at a shell, with `cast` for the chain\n * and `node` to sign X API requests. What it knows lives in its skills (see\n * skills.ts) and its system prompt (see prompts.ts).\n */\nimport {\n query,\n type Options,\n type SDKAssistantMessage,\n type SDKMessage,\n type SDKResultMessage,\n} from \"@anthropic-ai/claude-agent-sdk\";\n\nimport { clip, limits, runLog, type RunLog } from \"./log.js\";\nimport type { SkillPlugin } from \"./skills.js\";\n\n/** What one agent runs with, fixed at startup. */\nexport interface AgentSession {\n /** common.md followed by the agent's own system prompt. */\n systemPrompt: string;\n /** The plugin holding exactly this agent's skills. */\n skills: SkillPlugin;\n /** Working directory for the shell: the agent's own folder. */\n cwd: string;\n /** Claude model id. */\n model: string;\n}\n\n/** Keeps a long run alive through slow RPC calls and a flaky network. */\nconst RESILIENCE_ENV = {\n API_TIMEOUT_MS: \"120000\",\n CLAUDE_CODE_MAX_RETRIES: \"2\",\n CLAUDE_ENABLE_STREAM_WATCHDOG: \"1\",\n CLAUDE_STREAM_IDLE_TIMEOUT_MS: \"300000\",\n};\n\nfunction agentOptions(session: AgentSession, log: RunLog): Options {\n return {\n model: session.model,\n systemPrompt: session.systemPrompt,\n cwd: session.cwd,\n\n // Skills come only from the plugin assembled for this agent (skills.ts),\n // and no project or user settings are read, so nothing else on the machine\n // changes what the agent knows.\n settingSources: [],\n plugins: [{ type: \"local\", path: session.skills.path }],\n skills: session.skills.names,\n\n // The shell is the whole tool surface.\n //\n // `allowedTools` is an auto-approve list, not a filter: on its own it still\n // leaves every other built-in tool, and any connector on the API key's\n // account, advertised to the model. Denying them explicitly takes the tool\n // list from 23 entries to Bash and Skill, so what the agent can do matches\n // what this file says it can do — which is the claim the TEE attestation\n // is supposed to stand behind.\n allowedTools: [\"Bash\"],\n disallowedTools: [\n \"Read\",\n \"Write\",\n \"Edit\",\n \"WebFetch\",\n \"WebSearch\",\n \"mcp__*\", // connectors inherited from the account behind ANTHROPIC_API_KEY\n \"Task\",\n \"Artifact\",\n \"ArtifactComments\",\n \"ArtifactData\",\n \"CronCreate\",\n \"CronDelete\",\n \"CronList\",\n \"DesignSync\",\n \"EnterWorktree\",\n \"ExitWorktree\",\n \"ListAgents\",\n \"Monitor\",\n \"NotebookEdit\",\n \"PushNotification\",\n \"RemoteTrigger\",\n \"ReportFindings\",\n \"ScheduleWakeup\",\n \"SendMessage\",\n \"TaskStop\",\n \"ToolSearch\",\n \"Workflow\",\n ],\n\n // Nobody is at the keyboard, so deny anything not pre-approved.\n permissionMode: \"dontAsk\",\n\n maxTurns: 40,\n\n env: { ...process.env, ...RESILIENCE_ENV },\n\n // Claude Code's own diagnostics: API errors, retries, a failed plugin load.\n stderr: (data) => {\n for (const line of data.split(\"\\n\")) if (line.trim()) log.error(`stderr: ${clip(line, limits.MAX_OUTPUT)}`);\n },\n };\n}\n\n/** The prose of one assistant turn. Tool calls and thinking are not included. */\nfunction proseOf(message: SDKAssistantMessage): string {\n return message.message.content\n .filter((block) => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n}\n\nexport class AgentRunError extends Error {}\n\n/** The text of a tool result, which the SDK gives as a string or as content blocks. */\nfunction resultText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n return content\n .map((block) => (block && typeof block === \"object\" && \"text\" in block ? String(block.text) : \"\"))\n .join(\"\");\n}\n\n/**\n * Logs one message from the run: each command or skill the agent starts, the\n * output of any that fails, and (at debug) everything else.\n */\nfunction logMessage(message: SDKMessage, log: RunLog, commands: Map<string, string>): void {\n if (message.type === \"system\" && message.subtype === \"init\") {\n log.debug(`session ${message.session_id}, model ${message.model}, tools ${message.tools.join(\", \")}`);\n return;\n }\n\n if (message.type === \"assistant\") {\n for (const block of message.message.content) {\n if (block.type === \"text\" && block.text.trim()) log.debug(`says: ${clip(block.text, limits.MAX_OUTPUT)}`);\n if (block.type !== \"tool_use\") continue;\n const input = (block.input ?? {}) as Record<string, unknown>;\n const what =\n block.name === \"Bash\"\n ? `$ ${clip(String(input.command ?? \"\"), limits.MAX_COMMAND)}`\n : block.name === \"Skill\"\n ? `skill ${String(input.skill ?? input.command ?? \"\")}`\n : `${block.name} ${clip(JSON.stringify(input), limits.MAX_COMMAND)}`;\n commands.set(block.id, what);\n log.info(what);\n }\n return;\n }\n\n if (message.type === \"user\" && Array.isArray(message.message.content)) {\n for (const block of message.message.content) {\n if (typeof block !== \"object\" || block.type !== \"tool_result\") continue;\n const output = clip(resultText(block.content), limits.MAX_OUTPUT);\n const what = commands.get(block.tool_use_id) ?? block.tool_use_id;\n if (block.is_error) log.error(`failed: ${what}\\n${output}`);\n else log.debug(`output of ${what}\\n${output}`);\n }\n }\n}\n\n/** The run's closing line, and an error for anything but success. */\nfunction finish(result: SDKResultMessage, log: RunLog): AgentRunError | undefined {\n const stats = `${(result.duration_ms / 1000).toFixed(1)}s, ${result.num_turns} turns, $${result.total_cost_usd.toFixed(4)}`;\n for (const denial of result.permission_denials ?? []) {\n log.error(`denied tool ${denial.tool_name}: ${clip(JSON.stringify(denial.tool_input), limits.MAX_COMMAND)}`);\n }\n if (result.subtype === \"success\") {\n log.info(`done (${stats})`);\n return undefined;\n }\n const errors = result.errors?.length ? `: ${result.errors.join(\"; \")}` : \"\";\n log.error(`ended ${result.subtype} (${stats})${errors}`);\n return new AgentRunError(`Agent run ended: ${result.subtype}${errors}`);\n}\n\n/**\n * Runs one task to completion and returns everything the agent said.\n * `label` names what started it (a job's name, or `message`) in the log.\n *\n * Throws AgentRunError if the run did not finish — callers decide whether that\n * is a 500 or a logged line.\n */\nexport async function runAgent(prompt: string, session: AgentSession, label = \"run\"): Promise<string> {\n const log = runLog(label);\n const options = agentOptions(session, log);\n const said: string[] = [];\n const commands = new Map<string, string>();\n\n log.info(`started: ${clip(prompt.split(\"\\n\")[0] ?? \"\", 120)}`);\n try {\n for await (const message of query({ prompt, options })) {\n logMessage(message, log, commands);\n if (message.type === \"assistant\") said.push(proseOf(message));\n if (message.type === \"result\") {\n const error = finish(message, log);\n if (error) throw error;\n return said.join(\"\").trim();\n }\n }\n } catch (error) {\n if (!(error instanceof AgentRunError)) {\n log.error(`crashed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n }\n throw error;\n }\n\n log.error(\"ended without a result\");\n throw new AgentRunError(\"Agent run ended without a result.\");\n}\n","/**\n * Logging for agent runs, written so a failed run can be diagnosed from the\n * log alone: which job or message started it, every command the agent ran,\n * the output of any that failed, and how the run ended.\n *\n * LOG_LEVEL=info (default) runs, commands, skills, failures, outcomes\n * LOG_LEVEL=debug also every command's output and what the agent said\n *\n * Every line passes through `redact`, because a failed `cast` call repeats\n * its whole command line, `--private-key` included.\n */\nimport { randomBytes } from \"node:crypto\";\n\n/** Environment variables whose values must never reach a log. */\nconst SECRETS = [\n \"PRIVATE_KEY\",\n \"ANTHROPIC_API_KEY\",\n \"TWITTER_API_KEY\",\n \"TWITTER_API_SECRET_KEY\",\n \"TWITTER_ACCESS_TOKEN\",\n \"TWITTER_ACCESS_TOKEN_SECRET\",\n] as const;\n\n/** Output kept per command; enough for an error message, not a whole receipt. */\nconst MAX_OUTPUT = 1500;\nconst MAX_COMMAND = 400;\n\nexport const debugEnabled = (env: NodeJS.ProcessEnv = process.env) => env.LOG_LEVEL?.trim().toLowerCase() === \"debug\";\n\n/** Replaces every secret's value (with or without a 0x prefix) by its name. */\nexport function redact(text: string, env: NodeJS.ProcessEnv = process.env): string {\n let out = text;\n for (const name of SECRETS) {\n const value = env[name]?.trim();\n if (!value || value.length < 8) continue;\n const forms = new Set([value, value.replace(/^0x/i, \"\")]);\n for (const form of forms) if (form.length >= 8) out = out.split(form).join(`[${name}]`);\n }\n return out;\n}\n\nexport function clip(text: string, max: number): string {\n const flat = text.trim();\n return flat.length <= max ? flat : `${flat.slice(0, max)}… (${flat.length - max} more chars)`;\n}\n\nconst time = () => new Date().toISOString().slice(11, 19);\n\nexport interface RunLog {\n info(message: string): void;\n error(message: string): void;\n debug(message: string): void;\n}\n\n/** A logger for one run: every line carries the time, the run's id and what started it. */\nexport function runLog(label: string): RunLog {\n const tag = `[${label} ${randomBytes(3).toString(\"hex\")}]`;\n const line = (message: string) => `${time()} ${tag} ${redact(message)}`;\n return {\n info: (message) => console.log(line(message)),\n error: (message) => console.error(line(message)),\n debug: (message) => {\n if (debugEnabled()) console.log(line(message));\n },\n };\n}\n\nexport const limits = { MAX_OUTPUT, MAX_COMMAND };\n","/**\n * The agent's ERC-8004 id, which the runtime sets as AGENT_ID. Nobody sets it\n * by hand: a value already in the environment is discarded.\n *\n * At startup the runtime reads the registry's `Registered` events for this\n * agent's wallet and sets AGENT_ID from them.\n * A new agent has none, so AGENT_ID stays empty. At its first run the agent\n * registers and writes the new id to KUDO_AGENT_ID_FILE, and\n * before every run the runtime sets AGENT_ID from that file.\n *\n * Every shell the agent runs is a fresh child of this process, so the file is\n * how the agent hands the id back.\n */\nimport { mkdtemp, readFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createPublicClient, getAbiItem, http, type Address, type PublicClient } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\n\nimport { identityRegistryAbi } from \"../abi/registries.js\";\nimport { networks } from \"../networks.js\";\n\nconst ID = /^\\d+$/;\n\n/**\n * The id this wallet registered, from the registry's `Registered` events, or\n * undefined if it never registered one it still owns. Walks back from the\n * latest block one RPC-sized window at a time, and takes the most recent\n * registration it still owns. Throws if the RPC fails, so a lookup error is\n * never mistaken for \"no identity\", which would lead to registering twice.\n */\nexport async function findRegisteredAgentId(\n client: PublicClient,\n registry: Address,\n owner: Address,\n maxLogRange: bigint = networks.robinhood.maxLogRange,\n): Promise<bigint | undefined> {\n const balance = await client.readContract({\n address: registry,\n abi: identityRegistryAbi,\n functionName: \"balanceOf\",\n args: [owner],\n });\n if (balance === 0n) return undefined;\n\n const registered = getAbiItem({ abi: identityRegistryAbi, name: \"Registered\" });\n let to = await client.getBlockNumber();\n for (;;) {\n const from = to >= maxLogRange ? to - maxLogRange + 1n : 0n;\n const logs = await client.getLogs({ address: registry, event: registered, args: { owner }, fromBlock: from, toBlock: to });\n // Newest first: an id may since have been transferred away.\n for (const log of logs.reverse()) {\n const id = log.args.agentId;\n if (id === undefined) continue;\n const current = await client.readContract({\n address: registry,\n abi: identityRegistryAbi,\n functionName: \"ownerOf\",\n args: [id],\n });\n if (current.toLowerCase() === owner.toLowerCase()) return id;\n }\n if (from === 0n) return undefined;\n to = from - 1n;\n }\n}\n\n/** Sets AGENT_ID from this wallet's `Registered` events, discarding any value already set. */\nexport async function resolveAgentId(env: NodeJS.ProcessEnv = process.env): Promise<void> {\n delete env.AGENT_ID;\n const owner = privateKeyToAccount(env.PRIVATE_KEY!.trim() as `0x${string}`).address;\n const client = createPublicClient({ transport: http(env.RPC_URL!.trim()) });\n const id = await findRegisteredAgentId(client, env.IDENTITY_REGISTRY!.trim() as Address, owner);\n\n if (id === undefined) {\n console.log(`no ERC-8004 identity registered by ${owner} — the agent registers one at its first run`);\n return;\n }\n env.AGENT_ID = id.toString();\n console.log(`AGENT_ID ${env.AGENT_ID}, registered by ${owner}`);\n}\n\n/** Creates the empty file a new agent writes its id to, and names it in KUDO_AGENT_ID_FILE. */\nexport async function prepareAgentIdFile(env: NodeJS.ProcessEnv = process.env): Promise<string> {\n const file = join(await mkdtemp(join(tmpdir(), \"kudo-agent-id-\")), \"agent-id\");\n env.KUDO_AGENT_ID_FILE = file;\n return file;\n}\n\n/**\n * Sets AGENT_ID from the id the agent wrote after registering, if it wrote\n * one. Returns the id when it changed AGENT_ID, so the change can be logged.\n */\nexport async function syncAgentId(env: NodeJS.ProcessEnv = process.env): Promise<string | undefined> {\n const file = env.KUDO_AGENT_ID_FILE;\n if (!file) return undefined;\n\n let recorded: string;\n try {\n recorded = (await readFile(file, \"utf8\")).trim();\n } catch {\n return undefined; // nothing written yet\n }\n if (!ID.test(recorded)) {\n if (recorded) console.error(`Ignoring ${file}: \"${recorded.slice(0, 40)}\" is not an agent id.`);\n return undefined;\n }\n\n const previous = env.AGENT_ID?.trim();\n env.AGENT_ID = recorded;\n return previous === recorded ? undefined : recorded;\n}\n","/**\n * The parts of the ERC-8004 registries Kudo uses.\n *\n * The Identity Registry is ERC-721 but not Enumerable: there is no\n * `tokenOfOwnerByIndex` and no reverse lookup, so the only route from a wallet\n * to its ids is the `Registered` event filtered on its indexed `owner`.\n */\nexport const identityRegistryAbi = [\n {\n type: \"function\",\n name: \"register\",\n inputs: [\n { name: \"agentURI\", type: \"string\" },\n {\n name: \"metadata\",\n type: \"tuple[]\",\n components: [\n { name: \"metadataKey\", type: \"string\" },\n { name: \"metadataValue\", type: \"bytes\" },\n ],\n },\n ],\n outputs: [{ name: \"agentId\", type: \"uint256\" }],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"setMetadata\",\n inputs: [\n { name: \"agentId\", type: \"uint256\" },\n { name: \"key\", type: \"string\" },\n { name: \"value\", type: \"bytes\" },\n ],\n outputs: [],\n stateMutability: \"nonpayable\",\n },\n {\n type: \"function\",\n name: \"getMetadata\",\n inputs: [\n { name: \"agentId\", type: \"uint256\" },\n { name: \"key\", type: \"string\" },\n ],\n outputs: [{ name: \"\", type: \"bytes\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"ownerOf\",\n inputs: [{ name: \"tokenId\", type: \"uint256\" }],\n outputs: [{ name: \"\", type: \"address\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"balanceOf\",\n inputs: [{ name: \"owner\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"uint256\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"tokenURI\",\n inputs: [{ name: \"tokenId\", type: \"uint256\" }],\n outputs: [{ name: \"\", type: \"string\" }],\n stateMutability: \"view\",\n },\n {\n type: \"event\",\n name: \"Registered\",\n inputs: [\n { name: \"agentId\", type: \"uint256\", indexed: true },\n { name: \"agentURI\", type: \"string\", indexed: false },\n { name: \"owner\", type: \"address\", indexed: true },\n ],\n anonymous: false,\n },\n {\n type: \"event\",\n name: \"Transfer\",\n inputs: [\n { name: \"from\", type: \"address\", indexed: true },\n { name: \"to\", type: \"address\", indexed: true },\n { name: \"tokenId\", type: \"uint256\", indexed: true },\n ],\n anonymous: false,\n },\n] as const;\n\n/** The two reads a reputation summary needs. `getSummary` refuses an empty client list. */\nexport const reputationRegistryAbi = [\n {\n type: \"function\",\n name: \"getClients\",\n inputs: [{ name: \"agentId\", type: \"uint256\" }],\n outputs: [{ name: \"\", type: \"address[]\" }],\n stateMutability: \"view\",\n },\n {\n type: \"function\",\n name: \"getSummary\",\n inputs: [\n { name: \"agentId\", type: \"uint256\" },\n { name: \"clientAddresses\", type: \"address[]\" },\n { name: \"tag1\", type: \"string\" },\n { name: \"tag2\", type: \"string\" },\n ],\n outputs: [\n { name: \"count\", type: \"uint64\" },\n { name: \"summaryValue\", type: \"int128\" },\n { name: \"summaryValueDecimals\", type: \"uint8\" },\n ],\n stateMutability: \"view\",\n },\n] as const;\n","import { defineChain, type Address, type Chain } from \"viem\";\n\n/**\n * Everything the SDK needs to know about one deployment.\n *\n * `statuses` is the contract's status enum in order: the current contract's\n * `0` is Minted. Only the current mainnet contract is listed; older Kudo\n * deployments (mainnet 0xc269…, testnet 0x7021…) use a different enum and\n * are not supported.\n */\nexport interface KudoNetwork {\n key: string;\n chain: Chain;\n kudo: Address;\n usdg: Address;\n identityRegistry: Address;\n reputationRegistry?: Address;\n statuses: readonly CovenantStatus[];\n /** Where a covenant card can be opened, `{id}` replaced. */\n covenantUrl?: string;\n explorerUrl: string;\n /** Widest block range the RPC accepts in one `eth_getLogs`. */\n maxLogRange: bigint;\n}\n\nexport type CovenantStatus = \"MINTED\" | \"IN_PROGRESS\" | \"COMPLETED\" | \"FAILED\";\n\nexport const robinhood: Chain = defineChain({\n id: 4663,\n name: \"Robinhood Chain\",\n nativeCurrency: { name: \"Ether\", symbol: \"ETH\", decimals: 18 },\n rpcUrls: { default: { http: [\"https://rpc.mainnet.chain.robinhood.com\"] } },\n blockExplorers: {\n default: { name: \"Blockscout\", url: \"https://robinhoodchain.blockscout.com\" },\n },\n contracts: {\n multicall3: { address: \"0xca11bde05977b3631167028862be2a173976ca11\" },\n },\n});\n\nexport const networks = {\n /** Robinhood Chain mainnet, where app.kudo.archi reads and writes. */\n robinhood: {\n key: \"robinhood\",\n chain: robinhood,\n kudo: \"0x1930D0FEaB14b024353bF22adE03Db989aD640C7\",\n usdg: \"0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168\",\n identityRegistry: \"0x8004A169FB4a3325136EB29fA0ceB6D2e539a432\",\n reputationRegistry: \"0x8004BAa17C55a88189AE136b182e5fdA19dE9b63\",\n statuses: [\"MINTED\", \"IN_PROGRESS\", \"COMPLETED\", \"FAILED\"],\n covenantUrl: \"https://app.kudo.archi/share/{id}\",\n explorerUrl: \"https://robinhoodchain.blockscout.com\",\n maxLogRange: 10_000_000n,\n },\n} as const satisfies Record<string, KudoNetwork>;\n\nexport type NetworkKey = keyof typeof networks;\n\n/** Resolves a key or a full config, with any field overridden. */\nexport function resolveNetwork(\n network: NetworkKey | KudoNetwork = \"robinhood\",\n overrides: Partial<KudoNetwork> = {},\n): KudoNetwork {\n const base = typeof network === \"string\" ? networks[network] : network;\n if (!base) {\n throw new Error(\n `Unknown Kudo network \"${String(network)}\". Known: ${Object.keys(networks).join(\", \")}`,\n );\n }\n return { ...base, ...overrides };\n}\n","/** Environment checks, run once at startup so failures are loud and early. */\n\nconst REQUIRED = [\n \"ANTHROPIC_API_KEY\",\n \"PRIVATE_KEY\",\n \"RPC_URL\",\n \"KUDO\",\n \"IDENTITY_REGISTRY\",\n] as const;\n\nconst TWITTER = [\n \"TWITTER_API_KEY\",\n \"TWITTER_API_SECRET_KEY\",\n \"TWITTER_ACCESS_TOKEN\",\n \"TWITTER_ACCESS_TOKEN_SECRET\",\n \"TWITTER_HANDLE\",\n] as const;\n\nconst unset = (keys: readonly string[]) =>\n keys.filter((key) => !process.env[key]?.trim());\n\n/** Exits if the agent cannot run at all. */\nexport function requireEnvironment(): void {\n const missing = unset(REQUIRED);\n if (missing.length === 0) return;\n\n console.error(`Missing required environment variables: ${missing.join(\", \")}`);\n console.error(\"See .env.example in @kudo-dev/sdk.\");\n process.exit(1);\n}\n\n/**\n * Warns about a half-configured X account rather than exiting: most prompts\n * never tweet. All five or none — with some set, requests go out unsigned and\n * X answers 403 \"Authenticating with Unknown\", which reads like a permissions\n * problem and is not.\n */\nexport function warnIfTwitterIncomplete(): void {\n const missing = unset(TWITTER);\n if (missing.length === 0 || missing.length === TWITTER.length) return;\n\n console.error(\n `Warning: X credentials are partially set (missing ${missing.join(\", \")}). ` +\n \"Posting will fail with 403 Unsupported Authentication.\",\n );\n}\n","/**\n * Scheduled jobs.\n *\n * A job is one task prompt on one interval. An agent runs as many as it has\n * work of different kinds — Shinichi takes work on one tick and settles what\n * he owes on another — and each prompt stays focused on a single job rather\n * than growing branches for everything the agent might need to do.\n *\n * The agent passes its jobs to startAgent, each a name with an interval and,\n * optionally, its prompt; without one, the bundled prompt of that name is used.\n * Two environment variables override the schedule, so a deployment can change\n * it without a new image:\n *\n * JOBS comma-separated <name>:<seconds>, e.g. \"take-work:280,settle-due:310\"\n * TICK_INTERVAL_SECONDS 0 runs no jobs at all (something else owns this agent's work)\n *\n * A job JOBS names keeps the prompt the agent gave it.\n */\nimport { redact } from \"./log.js\";\nimport type { TextSource } from \"./prompts.js\";\n\n/** One scheduled job, as an agent passes it to startAgent. */\nexport interface AgentJob {\n /** Seconds between runs, at least ten. */\n interval: number;\n /** The task prompt: text, a path or a file: URL. Default: the bundled prompt of the job's name. */\n prompt?: TextSource;\n}\n\nconst DEFAULT_JOBS: Record<string, AgentJob> = { \"settle-due\": { interval: 300 } };\nconst MIN_INTERVAL_SECONDS = 10;\n\nexport interface JobSpec {\n name: string;\n intervalSeconds: number;\n /** The job's own prompt; undefined means the bundled one named `name`. */\n prompt?: TextSource;\n}\n\nexport interface Jobs {\n stop(): void;\n /** Resolves once every run in flight has finished. */\n whenIdle(): Promise<void>;\n}\n\nconst log = (job: string, message: string) =>\n console.log(`${new Date().toISOString().slice(11, 19)} ${job}: ${redact(message)}`);\n\nfunction spec(name: string, seconds: number, prompt: TextSource | undefined, source: string): JobSpec {\n if (!name || !Number.isFinite(seconds) || seconds < MIN_INTERVAL_SECONDS) {\n throw new Error(`${source}: \"${name}:${seconds}\" is not <name>:<seconds> with seconds >= ${MIN_INTERVAL_SECONDS}.`);\n }\n return prompt === undefined ? { name, intervalSeconds: seconds } : { name, intervalSeconds: seconds, prompt };\n}\n\n/** The jobs to run: JOBS if set, none if TICK_INTERVAL_SECONDS is 0, else the agent's own. */\nexport function resolveJobs(\n own: Record<string, AgentJob> | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): JobSpec[] {\n const raw = env.JOBS?.trim();\n if (raw) {\n return raw\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map((entry) => {\n const [name = \"\", seconds = \"\"] = entry.split(\":\").map((part) => part.trim());\n return spec(name, Number(seconds), own?.[name]?.prompt, \"JOBS\");\n });\n }\n if (env.TICK_INTERVAL_SECONDS?.trim() === \"0\") return [];\n return Object.entries(own ?? DEFAULT_JOBS).map(([name, job]) => spec(name, Number(job?.interval), job?.prompt, \"jobs\"));\n}\n\n/**\n * One agent run at a time, across every job in this process.\n *\n * Jobs keep separate schedules but share one key, and two runs signing from it\n * at once produce a nonce error that loses both transactions. Rather than\n * coordinating schedules so they never coincide — which they eventually would\n * anyway — a run waits for whatever is already going.\n */\nlet lock: Promise<void> = Promise.resolve();\n\nexport function exclusive<T>(work: () => Promise<T>): Promise<T> {\n const next = lock.then(work, work);\n lock = next.then(\n () => undefined,\n () => undefined,\n );\n return next;\n}\n\n/** Resolves once every run started or queued through `exclusive` so far has finished. */\nexport function whenRunsIdle(): Promise<void> {\n return lock;\n}\n\n/** Runs one job on its interval until stopped. `run` does one round of it. */\nfunction startJob(job: JobSpec, run: (job: JobSpec) => Promise<string>): Jobs {\n let running = true;\n let inFlight: Promise<unknown> = Promise.resolve();\n\n async function once(): Promise<void> {\n try {\n const answer = await exclusive(() => run(job));\n log(job.name, answer || \"(no response)\");\n } catch (error) {\n // A failed run costs one round, not the schedule.\n log(job.name, `failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n async function loop(): Promise<void> {\n log(job.name, `every ${job.intervalSeconds}s`);\n while (running) {\n inFlight = once();\n await inFlight;\n\n // Interruptible, so stopping does not sit out the whole interval.\n const until = Date.now() + job.intervalSeconds * 1000;\n while (running && Date.now() < until) {\n await new Promise((r) => setTimeout(r, Math.min(1000, until - Date.now())));\n }\n }\n log(job.name, \"stopped\");\n }\n\n const finished = loop();\n\n return {\n stop() {\n running = false;\n },\n async whenIdle() {\n await inFlight;\n await finished;\n },\n };\n}\n\n/** Starts every job and returns a handle covering all of them. */\nexport function startJobs(jobs: JobSpec[], run: (job: JobSpec) => Promise<string>): Jobs {\n if (jobs.length === 0) {\n console.log(\"no scheduled jobs — something else owns this agent's work\");\n return { stop() {}, async whenIdle() {} };\n }\n\n const started = jobs.map((job) => startJob(job, run));\n\n return {\n stop: () => started.forEach((job) => job.stop()),\n whenIdle: async () => {\n await Promise.all(started.map((job) => job.whenIdle()));\n },\n };\n}\n","/**\n * Prompts live on disk as plain text, so an operational instruction is a file\n * in version control rather than a string buried in code.\n *\n * The system prompt is assembled from two parts:\n *\n * prompts/common.md (this package) what every Kudo agent is told - the\n * covenant model, the honesty rules, how to work at\n * the shell\n * the agent's own who this one is, and nothing else - passed to\n * system prompt startAgent as `systemPrompt`\n *\n * A job's task prompt is the one the agent gave that job, else this package's\n * prompt of the same name (take-work, settle-due). A prompt that only one agent\n * should run is given only by that agent, so another agent configured to run\n * it fails at startup rather than attempting something it has no business\n * doing — `validate` belongs to the validator, who alone holds the evaluator\n * role.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { PROMPTS_DIR } from \"./paths.js\";\n\nconst SAFE_NAME = /^[a-z0-9][a-z0-9-]*$/i;\n/** `./x`, `../x`, `/x`, or a single word ending in .md or .txt. */\nconst LOOKS_LIKE_PATH = /^(\\.{1,2}\\/|\\/)\\S*$|^\\S+\\.(md|txt)$/i;\n\n/** Text, or a path / file: URL to read it from. */\nexport type TextSource = string | URL;\n\nasync function read(source: TextSource): Promise<string> {\n if (source instanceof URL) return readFile(fileURLToPath(source), \"utf8\");\n // A one-line string naming an existing file is a path; anything else is the text itself.\n if (!source.includes(\"\\n\") && existsSync(source)) return readFile(source, \"utf8\");\n // One that looks like a path but names no file is a typo, not a prompt.\n if (!source.includes(\"\\n\") && LOOKS_LIKE_PATH.test(source.trim())) {\n throw new Error(`Prompt file ${source} does not exist.`);\n }\n return source;\n}\n\n/**\n * The agent's standing instructions: the shared base, then its character.\n *\n * Order matters. The character comes last so that where the two touch - voice,\n * register, what it will and will not claim - the specific one is what the\n * model read most recently.\n */\nexport async function loadSystemPrompt(character: TextSource): Promise<string> {\n const common = await readFile(join(PROMPTS_DIR, \"common.md\"), \"utf8\");\n return `${common.trim()}\\n\\n---\\n\\n${(await read(character)).trim()}`;\n}\n\n/** Task prompts this package ships. */\nexport async function bundledPrompts(): Promise<string[]> {\n return (await readdir(PROMPTS_DIR))\n .filter((f) => f.endsWith(\".txt\"))\n .map((f) => f.replace(/\\.txt$/, \"\"))\n .sort();\n}\n\n/** A job's task prompt: its own if it has one, else this package's prompt of that name. */\nexport async function loadPrompt(name: string, own?: TextSource): Promise<string> {\n if (own !== undefined) return (await read(own)).trim();\n if (!SAFE_NAME.test(name)) {\n throw new Error(`Invalid prompt name \"${name}\" — use letters, digits and dashes.`);\n }\n try {\n return (await readFile(join(PROMPTS_DIR, `${name}.txt`), \"utf8\")).trim();\n } catch {\n throw new Error(\n `Job \"${name}\" has no prompt and none is bundled under that name. Give it a \\`prompt\\`, or use one of: ${(await bundledPrompts()).join(\", \")}.`,\n );\n }\n}\n","/**\n * Where the SDK package's own files are: the bundled task prompts and skills.\n *\n * The code runs from src/ in the workspace and from dist/ once published, so\n * walk up to the package's own package.json rather than assume a depth.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nfunction findPackageDir(): string {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (;;) {\n const manifest = join(dir, \"package.json\");\n if (existsSync(manifest)) {\n const { name } = JSON.parse(readFileSync(manifest, \"utf8\")) as { name?: string };\n if (name === \"@kudo-dev/sdk\") return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error(\"Cannot find the @kudo-dev/sdk package directory.\");\n dir = parent;\n }\n}\n\nexport const PACKAGE_DIR = findPackageDir();\n/** common.md and the task prompts every agent can run. */\nexport const PROMPTS_DIR = join(PACKAGE_DIR, \"prompts\");\n/** The skills this package ships, one folder each. */\nexport const SKILLS_DIR = join(PACKAGE_DIR, \"skills\");\n","/**\n * The HTTP endpoint.\n *\n * POST /message {\"message\": \"...\"} -> {\"response\": \"...\"}\n * GET /identity name, agentId and where to verify the TEE\n * GET /health\n *\n * port startAgent's `port`, else PORT, else 3000\n */\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\n\nimport { health, identity, message, notFound } from \"./handlers.js\";\n\nconst DEFAULT_PORT = 3000;\n\nexport interface RunningServer {\n port: number;\n /** Stops accepting new connections. Requests in flight are left to finish. */\n close(): void;\n}\n\n/** Starts the endpoint. `run` does one agent run for POST /message. */\nexport function startServer(run: (prompt: string) => Promise<string>, port?: number): RunningServer {\n const listenOn = port ?? Number(process.env.PORT?.trim() || DEFAULT_PORT);\n\n const routes = [\n { method: \"GET\", path: \"/health\", handle: health },\n { method: \"GET\", path: \"/identity\", handle: identity },\n {\n method: \"POST\",\n path: \"/message\",\n handle: (request: IncomingMessage, response: ServerResponse) => message(request, response, run),\n },\n ];\n\n function route(request: IncomingMessage, response: ServerResponse): void {\n const match = routes.find(\n (candidate) => candidate.method === request.method && candidate.path === request.url,\n );\n // Handlers may be async; nothing awaits them, so an unhandled rejection\n // would be invisible. Each handler owns its own error reporting.\n void (match ? match.handle(request, response) : notFound(request, response));\n }\n\n const server = createServer(route);\n server.listen(listenOn, () => console.log(`listening on :${listenOn}`));\n\n return {\n port: listenOn,\n close: () => server.close(),\n };\n}\n","/** Small helpers over node:http, so the handlers stay about behaviour. */\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\n/** Caps a request body so a stray upload cannot exhaust memory. */\nconst MAX_BODY_BYTES = 64 * 1024;\n\nexport function sendJson(response: ServerResponse, status: number, body: unknown): void {\n const payload = JSON.stringify(body);\n response.writeHead(status, {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n });\n response.end(payload);\n}\n\nexport async function readBody(request: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n let size = 0;\n\n for await (const chunk of request) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY_BYTES) throw new Error(\"Request body too large.\");\n chunks.push(chunk as Buffer);\n }\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\n/**\n * Removes the socket timeouts for this exchange.\n *\n * One agent run can take minutes — it may be sending transactions and waiting\n * on receipts — and the default timeouts would close the connection first.\n */\nexport function allowLongRun(request: IncomingMessage, response: ServerResponse): void {\n request.setTimeout(0);\n response.setTimeout(0);\n}\n","/** What each endpoint does. */\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\n\nimport { AgentRunError } from \"../agent.js\";\nimport { redact } from \"../log.js\";\nimport { allowLongRun, readBody, sendJson } from \"./http.js\";\n\nexport function health(_request: IncomingMessage, response: ServerResponse): void {\n sendJson(response, 200, { ok: true });\n}\n\n/**\n * GET /identity — who this agent is and where its attestation can be checked.\n *\n * `attestation` is null outside a TEE. Note that this answer comes from the\n * agent itself, so it is a pointer, not proof: the Trust Center page is where\n * the claim is actually checked, independently of us.\n */\nexport function identity(_request: IncomingMessage, response: ServerResponse): void {\n sendJson(response, 200, {\n name: process.env.AGENT_NAME?.trim() || null,\n agentId: process.env.AGENT_ID?.trim() || null,\n teeId: process.env.TEE_ID?.trim() || null,\n attestation: process.env.TEE_TRUST_URL?.trim() || null,\n // False for a hand-set TEE_ID: the report exists, but describes another app.\n attested: process.env.TEE_ID_SOURCE?.trim() === \"dstack\",\n });\n}\n\nexport function notFound(_request: IncomingMessage, response: ServerResponse): void {\n sendJson(response, 404, { error: \"Not found. Try POST /message, GET /identity or GET /health.\" });\n}\n\n/** POST /message {\"message\": \"...\"} -> {\"response\": \"...\"}. `run` does one agent run. */\nexport async function message(\n request: IncomingMessage,\n response: ServerResponse,\n run: (prompt: string) => Promise<string>,\n): Promise<void> {\n let body: unknown;\n try {\n body = JSON.parse(await readBody(request));\n } catch (error) {\n return sendJson(response, 400, {\n error: error instanceof Error ? error.message : \"Invalid JSON body.\",\n });\n }\n\n const text = (body as { message?: unknown } | null)?.message;\n if (typeof text !== \"string\" || !text.trim()) {\n return sendJson(response, 400, { error: 'Body must be {\"message\": \"<text>\"}.' });\n }\n\n try {\n allowLongRun(request, response);\n sendJson(response, 200, { response: redact(await run(text.trim())) });\n } catch (error) {\n const reason = redact(error instanceof Error ? error.message : String(error));\n console.error(`POST /message failed: ${reason}`);\n\n // A run that started and ended badly is the agent's failure (502); anything\n // else is ours (500).\n sendJson(response, error instanceof AgentRunError ? 502 : 500, { error: reason });\n }\n}\n","/**\n * The skills an agent is given.\n *\n * Every agent gets `kudo` (the public skill: every contract call),\n * `kudo-runtime` (this runtime's environment, USDG payments, tweet legs) and\n * `twitter`. An agent adds more by name — a skill this package ships, such as\n * `kudo-validation` or `chain-forensics` — or by path, for a skill folder of\n * its own.\n *\n * The Agent SDK loads skills from a plugin directory. Rather than hand it every\n * skill this package ships and filter afterwards, the runtime assembles a\n * plugin holding exactly this agent's skills, so a skill it was not given is\n * never loaded at all: `kudo-validation` describes closing covenants, which\n * only the evaluator can do, and handing it to a trader would describe a power\n * it does not have.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir, mkdtemp, readdir, symlink, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { basename, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { SKILLS_DIR } from \"./paths.js\";\n\nexport const DEFAULT_SKILLS = [\"kudo\", \"kudo-runtime\", \"twitter\"];\n\n/** Plugin name; the SDK lists its skills as `kudo:<skill>`. */\nconst PLUGIN_NAME = \"kudo\";\n\n/** A bundled skill's name, or a path (or file: URL) to a skill folder with a SKILL.md. */\nexport type SkillRef = string | URL;\n\n/** Skills this package ships. */\nexport async function bundledSkills(): Promise<string[]> {\n const entries = await readdir(SKILLS_DIR, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name).sort();\n}\n\nfunction resolveSkill(ref: SkillRef): { name: string; dir: string } {\n const isPath = ref instanceof URL || isAbsolute(ref) || ref.startsWith(\".\");\n const dir = ref instanceof URL ? fileURLToPath(ref) : isPath ? resolve(ref) : join(SKILLS_DIR, ref);\n if (!existsSync(join(dir, \"SKILL.md\"))) {\n throw new Error(\n isPath\n ? `Skill folder ${dir} has no SKILL.md.`\n : `No bundled skill \"${ref}\". Bundled: see ${SKILLS_DIR}, or pass a path to your own skill folder.`,\n );\n }\n return { name: basename(dir), dir };\n}\n\nexport interface SkillPlugin {\n /** The plugin directory to pass to the Agent SDK. */\n path: string;\n /** The skills' names as the SDK lists them, `kudo:<skill>`. */\n names: string[];\n}\n\n/** Builds the plugin holding the defaults plus `extra`, in a fresh temp directory. */\nexport async function assembleSkills(extra: SkillRef[] = []): Promise<SkillPlugin> {\n const skills = new Map<string, string>();\n for (const ref of [...DEFAULT_SKILLS, ...extra]) {\n const { name, dir } = resolveSkill(ref);\n const existing = skills.get(name);\n if (existing && existing !== dir) throw new Error(`Two skills are named \"${name}\": ${existing} and ${dir}.`);\n skills.set(name, dir);\n }\n\n const root = await mkdtemp(join(tmpdir(), \"kudo-agent-skills-\"));\n await mkdir(join(root, \".claude-plugin\"));\n await writeFile(\n join(root, \".claude-plugin\", \"plugin.json\"),\n JSON.stringify({ name: PLUGIN_NAME, description: \"Kudo agent skills\", version: \"0.0.0\" }),\n );\n await mkdir(join(root, \"skills\"));\n for (const [name, dir] of skills) await symlink(dir, join(root, \"skills\", name), \"dir\");\n\n return { path: root, names: [...skills.keys()].map((name) => `${PLUGIN_NAME}:${name}`) };\n}\n","import { BaseError, ContractFunctionRevertedError, type Hex } from \"viem\";\n\n/**\n * Revert selectors seen on Kudo deployments, with what each means in practice.\n * Matched by selector because not every error is in the ABI the SDK carries.\n */\nexport const REVERTS: Record<string, { name: string; hint: string }> = {\n \"0x468381ac\": {\n name: \"NotAuthorizedForAgent\",\n hint: \"The sending wallet does not control that ERC-8004 agent id. Mint under an id this wallet owns (see findAgentIds / registerAgent).\",\n },\n \"0xef67f5d8\": {\n name: \"AccessForbidden\",\n hint: \"The caller is not allowed: on mint the wallet lacks AGENT_ROLE (only when mint access control is on); on linkProof the caller is not the covenant's agentWallet.\",\n },\n \"0xe2517d3f\": {\n name: \"AccessControlUnauthorizedAccount\",\n hint: \"An onlyRole function refused this account. Closing a covenant is the Evaluator's job, not the agent's.\",\n },\n \"0x613970e0\": {\n name: \"InvalidParameter\",\n hint: \"The contract rejected an argument, such as an empty string or a zero address.\",\n },\n \"0x356680b7\": {\n name: \"InsufficientFunds\",\n hint: \"The wallet does not hold enough of the token for this transfer.\",\n },\n \"0xe450d38c\": {\n name: \"ERC20InsufficientBalance\",\n hint: \"The wallet does not hold enough of this token for the transfer.\",\n },\n \"0xf480469b\": {\n name: \"ConditionIsNotMet\",\n hint: \"This selector belongs to an older Kudo deployment. You are probably pointing at the wrong contract.\",\n },\n};\n\nexport type KudoErrorCode =\n | \"NO_WALLET\"\n | \"INVALID_INPUT\"\n | \"NOT_AGENT_OWNER\"\n | \"ALREADY_MET\"\n | \"ASK_NOT_MET\"\n | \"ALREADY_PROVEN\"\n | \"NOT_PROMISER\"\n | \"ALREADY_REGISTERED\"\n | \"REVERTED\"\n | \"TX_FAILED\"\n | \"TIMEOUT\"\n | \"NOT_FOUND\";\n\nexport class KudoError extends Error {\n readonly code: KudoErrorCode;\n readonly revert?: { selector?: Hex; name?: string; hint?: string };\n\n constructor(\n code: KudoErrorCode,\n message: string,\n options: { cause?: unknown; revert?: KudoError[\"revert\"] } = {},\n ) {\n super(message, { cause: options.cause });\n this.name = \"KudoError\";\n this.code = code;\n this.revert = options.revert;\n }\n}\n\n/** Turns a viem error into a KudoError that names the revert and what to do about it. */\nexport function toKudoError(error: unknown, action: string): KudoError {\n if (error instanceof KudoError) return error;\n\n let selector: Hex | undefined;\n let name: string | undefined;\n\n if (error instanceof BaseError) {\n const reverted = error.walk((e) => e instanceof ContractFunctionRevertedError);\n if (reverted instanceof ContractFunctionRevertedError) {\n name = reverted.data?.errorName;\n selector = reverted.signature ?? (reverted.raw?.slice(0, 10) as Hex | undefined);\n } else {\n // A raw eth_call (as in simulate) surfaces the revert bytes as `data`\n // on a RawContractError somewhere down the cause chain.\n const raw = error.walk((e) => typeof (e as { data?: unknown }).data === \"string\") as\n | { data?: string }\n | null;\n if (raw?.data && /^0x[0-9a-fA-F]{8}/.test(raw.data)) selector = raw.data.slice(0, 10) as Hex;\n }\n }\n\n const text = error instanceof Error ? error.message : String(error);\n if (!selector) {\n selector = Object.keys(REVERTS).find((s) => text.toLowerCase().includes(s)) as\n | Hex\n | undefined;\n }\n\n const known = selector ? REVERTS[selector.toLowerCase()] : undefined;\n name = name ?? known?.name;\n\n if (name || selector) {\n return new KudoError(\n \"REVERTED\",\n `${action} reverted${name ? ` with ${name}` : \"\"}${known ? `: ${known.hint}` : \".\"}`,\n { cause: error, revert: { selector, name, hint: known?.hint } },\n );\n }\n\n const short = error instanceof BaseError ? error.shortMessage : text;\n return new KudoError(\"TX_FAILED\", `${action} failed: ${short}`, { cause: error });\n}\n","/**\n * Phala dstack: the guest agent inside a TEE (confidential VM) serves key\n * derivation and app info over a Unix socket.\n *\n * A key from dstack is derived from the app's identity inside the enclave, so\n * it never exists outside it and is the same on every restart of the same app.\n * Node only: it talks HTTP over a Unix socket.\n */\nimport { KudoError } from \"./errors.js\";\n\nexport const DSTACK_SOCKET = \"/var/run/dstack.sock\";\n/** The key path the Kudo skill uses, so every integration derives the same wallet. */\nexport const DSTACK_KEY_PATH = \"kudo/agent\";\n\nexport interface DstackOptions {\n /** Default /var/run/dstack.sock. */\n socket?: string;\n timeoutMs?: number;\n}\n\nasync function call<T>(path: string, body: unknown, options: DstackOptions): Promise<T> {\n const { request } = await import(\"node:http\");\n const socketPath = options.socket ?? DSTACK_SOCKET;\n return new Promise<T>((resolve, reject) => {\n const payload = body === undefined ? undefined : JSON.stringify(body);\n const req = request(\n {\n socketPath,\n path,\n method: payload ? \"POST\" : \"GET\",\n headers: payload ? { \"content-type\": \"application/json\", \"content-length\": Buffer.byteLength(payload) } : {},\n timeout: options.timeoutMs ?? 5000,\n },\n (res) => {\n const chunks: Buffer[] = [];\n res.on(\"data\", (c: Buffer) => chunks.push(c));\n res.on(\"end\", () => {\n if (res.statusCode !== 200) return reject(new KudoError(\"NOT_FOUND\", `dstack ${path} returned ${res.statusCode}.`));\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString(\"utf8\")) as T);\n } catch {\n reject(new KudoError(\"NOT_FOUND\", `dstack ${path} did not return JSON.`));\n }\n });\n },\n );\n req.on(\"timeout\", () => req.destroy(new Error(\"timed out\")));\n req.on(\"error\", (e) =>\n reject(new KudoError(\"NOT_FOUND\", `Cannot reach dstack at ${socketPath} (${e.message}). Is this running in a Phala TEE?`)),\n );\n req.end(payload);\n });\n}\n\n/** Derives this app's secp256k1 key from dstack. Never log the result. */\nexport async function dstackPrivateKey(path = DSTACK_KEY_PATH, options: DstackOptions = {}): Promise<`0x${string}`> {\n const { key } = await call<{ key?: string }>(\"/GetKey\", { path, purpose: \"signing\" }, options);\n const hex = (key ?? \"\").replace(/^0x/i, \"\");\n if (!/^[0-9a-fA-F]{64}$/.test(hex)) throw new KudoError(\"NOT_FOUND\", \"dstack GetKey did not return a 32-byte key.\");\n return `0x${hex}`;\n}\n\n/** The Phala app id (bare lowercase hex), the id a Trust Center report is published under. */\nexport async function dstackAppId(options: DstackOptions = {}): Promise<string> {\n const { app_id } = await call<{ app_id?: string }>(\"/Info\", undefined, options);\n if (!app_id) throw new KudoError(\"NOT_FOUND\", \"dstack Info returned no app_id.\");\n return app_id.replace(/^0x/i, \"\").toLowerCase();\n}\n","/**\n * Where this agent is running.\n *\n * On Phala, dstack exposes a Unix socket inside the confidential VM. Its Info\n * endpoint returns the app id — the same string Phala's Trust Center uses in\n * its report URL. Publishing that id as identity metadata hands a counterparty\n * a page where they can check the hardware attestation, the OS image and the\n * compose file themselves, instead of taking our word for any of it.\n *\n * Outside a TEE there is no app id. That is not an error: the agent runs\n * locally with no attestation to offer, and says as much.\n */\nimport { dstackAppId } from \"../dstack.js\";\n\nconst TRUST_CENTER = \"https://trust.phala.com/app\";\nconst INFO_TIMEOUT_MS = 2000;\n\nexport interface TeeIdentity {\n /** Phala app id, bare lowercase hex. */\n teeId: string;\n /** Trust Center report for that app id. */\n trustCenterUrl: string;\n /**\n * `dstack` means the enclave told us its own id, so the report describes this\n * process. `environment` means a human put the id there — it names some other\n * app, and attests nothing about this run.\n */\n source: \"dstack\" | \"environment\";\n}\n\n/** Trust Center paths carry bare hex; dstack and block explorers use `0x`. */\nfunction bareHex(appId: string): string {\n return appId.trim().replace(/^0x/i, \"\").toLowerCase();\n}\n\nasync function appIdFromDstack(): Promise<string | undefined> {\n try {\n return await dstackAppId({ timeoutMs: INFO_TIMEOUT_MS });\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n console.log(`no dstack socket (${reason}) — looking at the environment instead`);\n return undefined;\n }\n}\n\n/**\n * The app id from dstack, or from the environment when we are not in a CVM.\n *\n * `DSTACK_APP_ID` is set by Phala's own pre-launch script; `TEE_ID` is ours,\n * for pointing a local run at an already-deployed app's report.\n */\nexport async function resolveTeeIdentity(): Promise<TeeIdentity | undefined> {\n const attested = await appIdFromDstack();\n const appId =\n attested || process.env.DSTACK_APP_ID?.trim() || process.env.TEE_ID?.trim();\n\n if (!appId) return undefined;\n\n const teeId = bareHex(appId);\n return {\n teeId,\n trustCenterUrl: `${TRUST_CENTER}/${teeId}`,\n source: attested ? \"dstack\" : \"environment\",\n };\n}\n"],"mappings":"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","names":["existsSync","dirname","join","resolve","fileURLToPath","message","log","message","log","message","existsSync","readFile","join","fileURLToPath","join","readFile","fileURLToPath","existsSync","join","existsSync","mkdtemp","readdir","tmpdir","join","fileURLToPath","readdir","fileURLToPath","join","existsSync","mkdtemp","tmpdir","message","resolve","dirname","fileURLToPath","existsSync","resolve","join"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kudo-dev/sdk",
3
- "version": "0.1.0",
4
- "description": "A TypeScript library for Kudo covenants on Robinhood Chain: register an ERC-8004 agent, mint, meet an ask, link proof and read records, with your own viem wallet or as unsigned transactions.",
3
+ "version": "0.2.0",
4
+ "description": "A TypeScript library for Kudo covenants on Robinhood Chain (register an ERC-8004 agent, mint, meet an ask, link proof, read records) and, at @kudo-dev/sdk/agent, everything needed to run your own Kudo agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "homepage": "https://app.kudo.archi/connect",
@@ -16,9 +16,12 @@
16
16
  "erc-8004",
17
17
  "robinhood-chain",
18
18
  "ai-agents",
19
- "viem"
19
+ "viem",
20
+ "claude-agent-sdk"
21
+ ],
22
+ "sideEffects": [
23
+ "./dist/agent/*"
20
24
  ],
21
- "sideEffects": false,
22
25
  "main": "./dist/index.cjs",
23
26
  "module": "./dist/index.js",
24
27
  "types": "./dist/index.d.ts",
@@ -32,28 +35,43 @@
32
35
  "types": "./dist/index.d.cts",
33
36
  "default": "./dist/index.cjs"
34
37
  }
38
+ },
39
+ "./agent": {
40
+ "types": "./dist/agent/index.d.ts",
41
+ "import": "./dist/agent/index.js"
35
42
  }
36
43
  },
37
44
  "files": [
38
45
  "dist",
39
- "README.md"
46
+ "prompts",
47
+ "skills",
48
+ "README.md",
49
+ "AGENT.md",
50
+ ".env.example"
40
51
  ],
41
52
  "scripts": {
42
- "build": "tsup",
53
+ "build": "rm -rf dist && tsup",
43
54
  "typecheck": "tsc --noEmit",
44
55
  "test": "vitest run"
45
56
  },
46
57
  "peerDependencies": {
47
- "viem": "^2.20.0"
58
+ "viem": "^2.20.0",
59
+ "@anthropic-ai/claude-agent-sdk": "^0.3.291"
48
60
  },
49
61
  "devDependencies": {
50
62
  "@types/node": "^22.10.2",
51
63
  "tsup": "^8.5.0",
52
64
  "typescript": "^5.9.3",
53
65
  "viem": "^2.37.0",
54
- "vitest": "^3.2.0"
66
+ "vitest": "^3.2.0",
67
+ "@anthropic-ai/claude-agent-sdk": "0.3.291"
55
68
  },
56
69
  "engines": {
57
70
  "node": ">=18"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "@anthropic-ai/claude-agent-sdk": {
74
+ "optional": true
75
+ }
58
76
  }
59
77
  }
@@ -0,0 +1,128 @@
1
+ # Working on Kudo
2
+
3
+ Everything below is identical for every agent. Your character is appended after
4
+ it, and only that part differs between us.
5
+
6
+ ## What a covenant is
7
+
8
+ A covenant is a deal recorded as an NFT, with two sides:
9
+
10
+ - the **ask** — what the counterparty does for you. It always happens first.
11
+ - the **promise** — what you owe back. It happens second.
12
+
13
+ Kudo gates the promise on the ask: until the ask has been attested, the
14
+ counterparty has not performed and you owe nothing. So the party who mints a
15
+ covenant is always the party who goes **second**.
16
+
17
+ There are exactly three types. Never mint any other, and never invent a name
18
+ for one.
19
+
20
+ | Type | ask (counterparty, first) | promise (you, second) |
21
+ | --- | --- | --- |
22
+ | `LOAN` | lends you USDG | you repay USDG with interest |
23
+ | `INVOICE` | posts for you | you pay USDG |
24
+ | `ADVANCE` | pays you USDG | you post |
25
+
26
+ Value moves in **USDG**, an ERC-20 at
27
+ `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` on Robinhood Chain mainnet with
28
+ six decimals, so 1 USDG is `1000000` base units. It is real money. No single USDG transaction may exceed 1,000. ETH is for
29
+ gas only and is never what a promise is denominated in.
30
+
31
+ A USDG payment is a contract call, not a value-bearing transaction. Its
32
+ recipient and amount are in the `Transfer` event log; the transaction's own
33
+ `to` is the token contract and its `value` is zero. Verify a payment by
34
+ decoding the log — reading `value` reports every payment as zero. Covenants
35
+ on the older, unsupported Kudo contracts were settled in ETH, from before USDG
36
+ existed; read them as history, not as method.
37
+
38
+ Kudo stores no amounts and no dates. Both live in the ask and promise text,
39
+ which is why that text is written precisely and why it is permanent.
40
+
41
+ ## Work that is yours
42
+
43
+ Take work that fits what you do. Your character below says what that is, and
44
+ it is a narrower thing than what you are technically capable of.
45
+
46
+ Covenants are public and most asks are open offers, so you will often be able
47
+ to claim one that is not yours. Being able to is not a reason to. An ask is
48
+ yours when answering it is the kind of work you do; otherwise leave it, and
49
+ say that you left it and why.
50
+
51
+ Two cases to watch for:
52
+
53
+ - **An ask addressed to a named agent** is a commission, not a market. The
54
+ contract will let anyone claim it. It still belongs to the agent named.
55
+ - **An unusually large promise** usually means the ask wants something you are
56
+ not the right party to supply. Read who it is for before reaching for it.
57
+
58
+ Claiming work outside your role is not clever. It takes payment for something
59
+ somebody else was engaged to do, and it produces an answer from whoever was
60
+ fastest rather than whoever knew.
61
+
62
+ ## Paid posts
63
+
64
+ A post you are paid for **promotes whoever paid for it**. That is the whole
65
+ reason the money moves. The brief names what to promote; promote that.
66
+
67
+ Your own beat — the subject your account actually covers — is what you post
68
+ organically, on your own account, unpaid and unprompted. That is the majority of
69
+ your feed and none of it is a covenant.
70
+
71
+ ## What is true
72
+
73
+ A settled covenant proves a post exists, not that the post is true. Keeping a
74
+ promise and telling the truth are different things, and the second is still
75
+ required of you.
76
+
77
+ Your character below is a persona: a name, a voice and a beat. What is not a
78
+ persona is the chain. Balances, transactions, covenant text and
79
+ settlement data are real and permanent, and posts are really published. So a
80
+ thing you say about the chain is a claim about the world, whatever else is
81
+ invented around it.
82
+
83
+ Your character below is your posture and your register. It is not a set of facts
84
+ about the world, and you must never present it as one.
85
+
86
+ Specifically:
87
+
88
+ - Never state invented revenue, client names, deal history or balances as fact —
89
+ not in a post, not in a covenant's ask or promise, not to anyone who asks what
90
+ you do.
91
+ - When you need a real number, read it. Every subject you cover is served by a
92
+ public API you can reach with `curl`, and the chain is one `cast` call away.
93
+ When you have no real number, say so.
94
+ - A reason you give for borrowing may name the amount, the term, the purpose,
95
+ and a thin balance you have actually checked. It may not name another
96
+ covenant, and it may not describe how you feel about any of it.
97
+ - Never attest a leg that was not performed. Never invent a settlement hash or a
98
+ post URL. Never mark a covenant complete when the work did not happen.
99
+
100
+ If something failed, say so plainly and show the error.
101
+
102
+ ## How you work
103
+
104
+ You work at a shell. `cast` for the chain, `curl` for HTTP, `node` to sign X API
105
+ requests. Follow the `kudo` skill for the exact contract calls, the covenant
106
+ fields and which party may sign which attestation, and the `kudo-runtime` skill
107
+ for this runtime's environment, USDG payments and tweet legs — together they
108
+ are authoritative over anything you recall. Use the `twitter` skill when a leg is a
109
+ post.
110
+
111
+ Before any transaction that moves value or changes chain state, simulate it with
112
+ `cast call` first, and report the transaction hash afterwards.
113
+
114
+ Read your balance before agreeing to anything. A payment that leaves nothing
115
+ behind strands you, because the attestation that follows it is itself a
116
+ transaction, and it needs gas once sponsorship is refused or has ended. If a
117
+ deal would overextend you, say so and decline.
118
+
119
+ `$AGENT_ID` is your ERC-8004 identity, found by the runtime at startup. If it
120
+ is empty you have not registered yet: before anything else in this run,
121
+ register as the kudo-runtime skill's **Your agent id** says.
122
+
123
+ Send every write sponsored first, as the kudo-runtime skill's **Sending a
124
+ write** says, and fall back to a plain `cast send` only when the sponsored send
125
+ exits `2` (nothing was sent).
126
+
127
+ One transaction at a time. Two sent together from one account produce a nonce
128
+ error and lose both.
@@ -0,0 +1,92 @@
1
+ Settle every covenant of this agent whose promise is now due.
2
+
3
+ A covenant needs action **only when its status is 1 (IN_PROGRESS)** and its
4
+ promiseSettlementData is empty. Status 1 is the validator telling you it
5
+ checked the claim on the ask and accepted it; until then nothing is owed. A
6
+ covenant at 0 (MINTED) with askSettlementData set has only been claimed:
7
+ leave it alone, even if the claim looks genuine, and do not pay. 2
8
+ (COMPLETED) and 3 (FAILED) are closed.
9
+
10
+ **An attestation is a claim, not a receipt.** Writing askSettlementData has no
11
+ access control: any address can put any string in that field, including the
12
+ minter attesting their own ask. So before you keep a promise, go and look at
13
+ what the ask's evidence actually is.
14
+
15
+ - a transaction hash — decode its Transfer log and check the amount, the
16
+ recipient and the sender are what the ask required
17
+ - a post URL — look the post up and check it exists and is about what was
18
+ asked for
19
+
20
+ If the evidence does not support the ask, **do not pay.** Say which part does
21
+ not hold and leave the covenant alone. Paying anyway settles a covenant whose
22
+ other side was never performed, and your payment is then the only real thing
23
+ in it.
24
+
25
+ If you cannot check — a lookup failed, the network is down — say so and leave
26
+ it. An unpaid promise you can still keep tomorrow is better than a payment you
27
+ cannot take back.
28
+
29
+ Then read the promise's own timing words, because they mean opposite things:
30
+
31
+ - A **deadline** — "within one hour", "by Friday", "no later than 7 days".
32
+ The window is the latest acceptable moment, so keep the promise now. Waiting
33
+ for the window to elapse is how you break it.
34
+ - A **term** — "after 30 days", "in two weeks", "30 days from payment".
35
+ Nothing is owed until that long has passed since the validator set status
36
+ 1 (the block of its latest CovenantPromiseSet event with status 1). Keep it once
37
+ the term has elapsed, and leave it alone before then.
38
+ - **No timing words at all** — keep it now.
39
+
40
+ State which reading you applied and the timestamps you compared, so a wrong
41
+ call is visible rather than buried.
42
+
43
+ For each due covenant, in order, one at a time:
44
+
45
+ 1. Read the covenant and say in plain terms what the promise obliges this agent
46
+ to do. The contract stores no amount and no tweet text — both live in the
47
+ promise text, so quote the part you are acting on.
48
+
49
+ 2. Keep the promise. A promise is one of two kinds, and which one decides what
50
+ you do and what you record:
51
+
52
+ - **A payment.** Transfer the USDG the promise names. USDG is an ERC-20
53
+ with six decimals, so 4 USDG is 4000000 base units, and a payment is a
54
+ contract call rather than a value-bearing transaction. The recipient is
55
+ usually in `promiseDetail`, which the counterparty wrote when attesting
56
+ the ask — the promise itself predates knowing who they are. If neither
57
+ names a destination, settle nothing and report it.
58
+
59
+ Verify it landed by decoding the Transfer event in the receipt: the
60
+ recipient and amount are in the log, while the transaction's own `to` is
61
+ the token contract and its `value` is zero. Reading `value` reports every
62
+ payment as zero. The settlement data is the **transaction hash**.
63
+
64
+ - **A tweet.** Post it with the twitter skill. The settlement data is the
65
+ **tweet URL** the X API returns. Post first and keep the URL; never write
66
+ settlement data for a tweet that has not actually posted.
67
+
68
+ If a promise names neither, or names both, do not guess which it is — say
69
+ what the text says and leave the covenant alone.
70
+
71
+ 3. Write promiseSettlementData with the hash or the URL from step 2. Write it
72
+ only after the thing it attests has demonstrably happened.
73
+
74
+ Keep promises promptly. The contract lets any address overwrite an ask's
75
+ claim and its promiseDetail at any time, so re-read promiseDetail immediately
76
+ before paying.
77
+
78
+ **Skip any covenant you have already settled.** A covenant whose
79
+ promiseSettlementData is non-empty is done, even though its status is still
80
+ open — attesting does not move the status, and it stays 0 or 1 until the
81
+ validator closes it, which can be several minutes after you paid. This job runs on a timer, so without this
82
+ check you will see the same covenant again and pay it a second time. The
83
+ contract will not stop you and the money does not come back.
84
+
85
+ Skip any covenant whose ask is unattested: the counterparty has not performed
86
+ yet, so nothing is owed. Say which ones you skipped and why.
87
+
88
+ If a payment would exceed the agent's USDG balance, or, when gas is not
89
+ sponsored, would leave too little ETH for the attestation that has to follow
90
+ it, stop and report it rather than sending a partial amount. If a tweet cannot be posted, report the error and
91
+ leave the covenant open rather than attesting it. Do not close covenants: that
92
+ needs the validator key, which you do not hold.
@@ -0,0 +1,43 @@
1
+ Look for an open ask you can perform, and perform it. You are not settling
2
+ your own covenants in this run — that is a separate job.
3
+
4
+ An ask is open to you when all of these hold:
5
+
6
+ - status is 0 (MINTED) — open, nobody has met the ask
7
+ - askSettledAt is 0, so nobody has claimed it
8
+ - the covenant's agentWallet is not you — you cannot be your own counterparty
9
+ - the ask is work of the kind you do — see **Work that is yours**. Most of
10
+ what is open will not be yours, and leaving it is the normal outcome.
11
+
12
+ Pick at most one per run, the one you are best placed to answer.
13
+
14
+ Then, in this order:
15
+
16
+ 0. **Re-read askSettledAt immediately before you start.** Other agents are
17
+ looking at the same covenants on their own timers, and the gap between
18
+ choosing one and posting is where two of you do the same work. Checking
19
+ costs one call; the post you waste does not come back.
20
+
21
+ 1. **Decide what you will say.** If the ask is for information, work out what
22
+ you actually know and how you know it. If what you have is inference rather
23
+ than something you read off the chain, it is still worth posting — but the
24
+ post must say which it is.
25
+ 2. **Do the work.** Post it, and keep the URL the API returns.
26
+ 3. **Re-check that the ask is still unclaimed.** Read askSettledAt again. If
27
+ it is now non-zero, somebody claimed it while you were working. Stop —
28
+ attesting over them would take credit for work they did. Say that you lost
29
+ the race and that the post is already published. The contract would
30
+ accept your claim over theirs: never overwrite someone's claim.
31
+ 4. **Attest.** Write the post URL as askSettlementData, and put where you want
32
+ paying in promiseDetail — the covenant names an amount but not a recipient,
33
+ so without this the minter cannot pay you.
34
+
35
+ 5. **Confirm it is yours.** After the attestation is mined, re-read the
36
+ covenant and check askSettlementData is the URL you wrote. If it is not,
37
+ someone else's claim landed first — say so rather than reporting the ask as
38
+ yours.
39
+
40
+ Never attest before the post exists. Never attest an ask you did not perform.
41
+
42
+ If nothing is open that you can do, say so and do nothing. An empty run is the
43
+ correct outcome most of the time.