# Theseus playground — context for AI assistants You are helping a user write, compile, and deploy an on-chain Theseus agent in this browser IDE. The full Theseus skill set follows. # Theseus Skills — flattened SKILL.md bundle Single-file concatenation of every Theseus skill (agentskills.io SKILL.md). Source of truth: the per-skill SKILL.md files under skills/. --- ## skill: theseus-agents --- name: theseus-agents description: >- Author, compile, deploy, and operate autonomous on-chain AI agents on the Theseus network (SHIP agents). Covers the project layout — THESEUS.md frontmatter (name, id, model, sovereignty, schedule, tags), the agent.rs CompiledAgent graph of nodes/actions/terminators (Model/Tool/Code roles; Goto/If/End), tools.yaml (native-tools allowlist, common-tools, byo-tools), and skills/*/SKILL.md via the skills!() macro — plus compiling to SCALE and registering via register_ship_agent (mode, value/endowment, payload, salt). Explains the compile→SCALE→blake2 hash+salt address derivation, sovereignty modes managed/autonomous/sovereign (managed CANNOT schedule), heartbeats (interval_blocks, allow_parallel, skip reasons), update_agent, call_agent, and set_agent_metadata. Use when the user wants to write/build/deploy/register a Theseus or SHIP agent, edit THESEUS.md or agent.rs or tools.yaml, choose a sovereignty mode, set up heartbeats, give an agent skills/tools, or debug agent registration and runs. license: MIT compatibility: >- For building Theseus SHIP agents. Hands-on use needs the Theseus toolchain: Rust, the repo's agent-compiler (and the skills!() proc macro), and a Theseus node (or the hosted playground) to submit register_ship_agent. Targets pallet_agents on theseus-chain spec_version 100 (pallet-revive 0.18.0). metadata: version: "0.2.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus Agents — write, compile, deploy, operate A Theseus agent is a small project directory the **agent-compiler** turns into a single SCALE-encoded `CompiledAgent`, which you register on-chain with `register_ship_agent`. The result is a real chain account that runs a deterministic graph, calls native tools, and (depending on its mode) wakes on a schedule. For the deep graph reference (every `Node` / `Action` / `Terminator` / `ExprSpec` field), see [references/graph-model.md](references/graph-model.md). For the native tool surface, see the **theseus-native-tools** skill. ## Agent project layout Four ingredients (the compiler reads all of them): - **`THESEUS.md`** — YAML frontmatter + the system prompt (everything after the closing `---`). The frontmatter configures identity and sovereignty. - **`agent.rs`** — a Rust function that builds and returns a `ship_types::CompiledAgent`: the `nodes` graph, `entry`, `state_fields`, `tools`, optional `input_schema`, optional `schedule`, and `skills`. - **`tools.yaml`** — declares the tool surface: `native-tools` (allowlist over the in-runtime surface), `common-tools` (chain-curated off-chain catalog), `byo-tools` (author-supplied executor with inline schema). - **`skills//SKILL.md`** — one folder per skill; auto-discovered by the `skills!()` proc macro and sorted for deterministic output. ## THESEUS.md frontmatter The playground validates this with a Zod schema; the native compiler parses the same fields via `parse_theseus_md`. Fields: | Field | Type | Required | Notes | | --- | --- | --- | --- | | `name` | string | yes | Human-readable agent name. | | `id` | string | yes | Stable agent id (e.g. `hello-agent-v1`). | | `model` | string | yes | Model tag (e.g. `claude-sonnet-4-6`); compiled to a `ModelId` via `model_id_from_tag`. | | `sovereignty` | `managed` \| `autonomous` \| `sovereign` | yes | Maps to `AgentMode` (see below). | | `schedule` / `interval_blocks` | integer | no | Heartbeat cadence in blocks. Only valid for autonomous/sovereign. | | `description` | string | no | Explorer profile text (≤1024 bytes on-chain). | | `tags` | string[] | no | Explorer tags (≤8 tags, ≤32 bytes each). | Everything after the frontmatter is the **system prompt**. The compiler injects it as `ExprSpec::String(system_prompt)` unless `agent.rs` sets one explicitly. ## The agent.rs graph model (summary) `agent.rs` returns a `CompiledAgent`. Key fields: `id`, `name`, `version`, `active`, `entry` (the starting `NodeId`), `system_prompt`, `state_fields`, `tools`, `nodes`, `input_schema`, `schedule`, `skills`, `native_allowlist`. Each `Node` has: - a `role`: `NodeRole::Model` (invoke the LLM), `NodeRole::Tool` (dispatch tool calls the model requested), or `NodeRole::Code` (deterministic state ops); - `actions` (e.g. `MessagesAppend`, `ModelInvoke`, `ToolsDispatch`); and - a `terminator`: `Terminator::Goto(node)`, `Terminator::If { condition, then_node, else_node }`, or `Terminator::End`. A typical loop is: **init** (append the user prompt) → **think** (`ModelInvoke`) → if the model emitted `tool_calls`, go to **act** (`ToolsDispatch`) and loop back to **think**; else **done** (`End`). Full field tables, `ExprSpec`, `StateField`, `NodePolicy`, and `MessageSource` are in [references/graph-model.md](references/graph-model.md). ## tools.yaml All three blocks are optional: ```yaml # native-tools — runtime-dispatched, in-process. No executor required. # block absent -> every native tool is available (the default) # present non-empty -> restrict to exactly these names # present empty [] -> no native tools (capabilities.* still work) native-tools: - tokens.balance - chain.transfer - contracts.call # common-tools — chain-curated off-chain catalog. Bare suffixes; the compiler # resolves them to offchain.. Opt-in. common-tools: - web_search - web_fetch # byo-tools — agent-supplied executor (the agent sets tool_operator). Inlined # JSON schema; the author owns the runtime that fulfils calls. byo-tools: http_fetch: description: Fetch a URL over HTTPS and return body as UTF-8. parameters: type: object properties: url: { type: string } required: [url] auto-activate: false ``` The `native-tools` list becomes the agent's `native_allowlist` (`None` ⇒ all natives allowed; `Some(names)` ⇒ the runtime intersects with the native subset on every model invocation). Unknown names fail registration (`NativeAllowlistContainsUnknown`); too many entries fails with `TooManyNativeAllowlistEntries` (`MaxNativeAllowlistLen = 32`). ## skills!() and the SKILL.md convention `agent_compiler::skills!()` walks `skills//SKILL.md` at compile time and embeds each as a `SkillSpec`. A skill's frontmatter (`name`, `description`, `allowed-tools`) plus its body teach the agent a focused capability; at runtime the agent activates a skill via `capabilities.describe` (see theseus-native-tools). Skills are sorted by name for deterministic output. ## Sovereignty modes (`AgentMode`) The mode is governance (who owns it) × funding (who pays for runs): | Mode | `owner` | Heartbeats | Funding | | --- | --- | --- | --- | | **Managed** | `Some(deployer)` | **No** — `ManagedAgentCannotSchedule` | Caller-funded (reactive only). | | **Autonomous** | `Some(deployer)` | Yes | Self-funded — pays heartbeats from its own balance. | | **Sovereign** | `None` | Yes | Self-funded; ownership fully relinquished. | The exact enum is `primitives::AgentMode { Managed, Autonomous, Sovereign }`. Pick `Managed` for an agent that only ever responds to explicit `call_agent` / contract requests; pick `Autonomous`/`Sovereign` for an agent that should run on a heartbeat. Only the owner can `update_agent`; a sovereign agent (no owner) can still update its own display profile via `agent.set_metadata`. ## Heartbeats Autonomous/Sovereign agents may carry a `ScheduleConfig { interval_blocks, next_run_at, allow_parallel }`. Each scheduled run charges the agent's own account. A scheduled run is **skipped** with a `ScheduledRunSkipReason` when: - `InsufficientAgentBalance` — the agent can't pay for the run; or - `PreviousRunInProgress` — a prior run is still in flight and `allow_parallel = false`. Set or clear the schedule at registration (via THESEUS.md `interval_blocks`) or later via `update_agent`'s `maybe_schedule_interval_blocks` (which preserves the existing `allow_parallel`). Managed agents cannot schedule at all. ## Compile → hash → address → register ### 1. Compile The agent-compiler (`examples/agent-compiler`, also built to WASM for the playground) does: 1. parse `THESEUS.md` → `AgentConfig` (id, name, model tag, system prompt, schedule); 2. parse `skills/` into `SkillSpec`s; 3. resolve the model tag → `ModelId` (`model_id_from_tag`); 4. walk `agent.rs` to extract the `CompiledAgent` literal; 5. apply THESEUS.md overrides (id/name/system_prompt/schedule/skills the `agent.rs` left to the config); 6. merge `tools.yaml` into `CompiledAgent.tools`; 7. validate the graph (entry in range, node ids resolve, …); 8. **SCALE-encode** → `Vec` (`scale_bytes`), plus diagnostics. The output is `{ scale_bytes, diagnostics }`. `scale_bytes` is the registration `payload`. ### 2. Address derivation The agent account is derived **deterministically** from the deployer, the compiled-bytes hash, and a salt (symmetric with `pallet_contracts`): ```text entropy = SCALE( ("theseus_agent_v1", deployer, compiled_hash, salt) ) agent_id = decode_into_AccountId( blake2_256(entropy) ) // trailing-zero padded ``` where `compiled_hash = T::Hashing::hash(payload)` (blake2). The same `(deployer, payload, salt)` always yields the same agent address; change the `salt` to mint a distinct agent from identical code. ### 3. Register Submit on `pallet_agents` (signed by the deployer): ```text register_ship_agent(mode, value, payload, salt) mode : AgentMode (Managed | Autonomous | Sovereign) value : Balance endowment transferred to the new agent account payload : the SCALE-encoded CompiledAgent (scale_bytes) salt : Vec mixed into the address derivation ``` On success it emits **`Registered`** with: `agent_id`, `owner`, `mode`, `name`, `compiled_hash`, `evm_address` (the agent's H160), `version`, `endowment`, `tools_count`, `system_prompt_hash`, `input_schema_hash`, and `models` (the distinct model tags used across the graph — what the explorer shows). Decode failures return `InvalidMessageEncoding`. ### 4. (optional) Set the explorer profile ```text set_agent_metadata(agent_id, description, avatar_uri, tags) ``` callable by the **owner** or by the **agent's own account** (sovereign self-governance). It updates display-only fields and does **not** change `compiled_hash`. Emits `MetadataSet`. ## Invoking and updating - **`call_agent(agent_id, value, input)`** — start a run. `value` is transferred to the agent first; `input` is a SCALE-encoded value matching the agent's `input_schema`. The agent must be `active` (else `AgentInactive`). Emits `RunStarted { trigger: Extrinsic, … }`. (See theseus-playground for the browser path and theseus-contracts for the contract-driven path.) - **`update_agent(agent_id, maybe_name, maybe_system_prompt, maybe_tools, maybe_compiled, maybe_tool_operator, maybe_active, maybe_schedule_interval_blocks)`** — owner-only (`NotAgentOwner` otherwise). Each field is optional; supplying `maybe_compiled` replaces the node graph and bumps `version`. Emits `Updated`. ## Minimal end-to-end example (hello-agent) `THESEUS.md`: ```markdown --- name: Hello Agent id: hello-agent-v1 model: claude-sonnet-4-6 sovereignty: managed description: A friendly greeter agent on the Theseus network. tags: [demo, greeter] --- You are a friendly greeter agent running on the Theseus network. Respond to one prompt at a time in a single warm, witty sentence. When the user asks about balance or holdings, use the `check-balance` skill before replying. ``` `agent.rs` (shape — full file in the repo's `examples/hello-agent`): ```rust use ship_types::{ Action, CompiledAgent, ExprSpec, MessageSource, ModelId, Node, NodePolicy, NodeRole, RecordField, Role, StateField, Terminator, TypeSpec, }; const THESEUS_MD: &str = include_str!("THESEUS.md"); pub fn hello_agent(model_id: ModelId) -> CompiledAgent { let config = agent_compiler::parse_theseus_md(THESEUS_MD) .expect("THESEUS.md must be well-formed"); let skills = agent_compiler::skills!(); // discovers skills/*/SKILL.md CompiledAgent { id: config.id, name: config.name, version: 1, ship_version: "1.0".into(), active: true, entry: 0, system_prompt: ExprSpec::String(config.system_prompt), state_fields: vec![ StateField { id: 0, name: "messages".into(), ty: TypeSpec::Array(Box::new(TypeSpec::String)) }, StateField { id: 1, name: "model_out".into(), ty: TypeSpec::String }, ], tools: vec![], nodes: make_nodes(model_id), // init -> think -> (act -> think) | done types: vec![], input_schema: Some(TypeSpec::Record(vec![RecordField { name: "prompt".into(), ty: TypeSpec::String, optional: false, }])), schedule: None, // managed => reactive only skills, native_allowlist: None, // all natives allowed } } ``` `tools.yaml`: leave `native-tools` absent to allow all natives, or pin a subset as shown above. Then: compile → get `scale_bytes` → `register_ship_agent(AgentMode::Managed, endowment, scale_bytes, salt)` → read `Registered.agent_id` → `call_agent(agent_id, 0, scale_encode({ prompt: "hi" }))`. In the browser, the playground does all of this from the **Deploy** button (see theseus-playground). ## Troubleshooting registration & runs - **`InvalidMessageEncoding`** — `payload` is not a valid SCALE `CompiledAgent` (recompile; don't hand-edit bytes). - **`EmptyNodes` / `InvalidEntry`** — the graph has no nodes, or `entry` is out of range / references an unknown node id. - **`ManagedAgentCannotSchedule`** — you set a schedule on a `Managed` agent; switch to `Autonomous`/`Sovereign` or drop the schedule. - **`NativeAllowlistContainsUnknown` / `TooManyNativeAllowlistEntries`** — a `native-tools` name is misspelled / not a real native, or you listed > 32. - **`ToolNameCollidesWithNative`** — a `byo-tools`/`common-tools` name shadows a native tool name. - **`NotAgentOwner`** — `update_agent`/`set_agent_metadata` from a non-owner (for sovereign agents only the agent's own account may set metadata). - **`AgentInactive`** — `call_agent` on an agent with `active = false` (re-enable via `update_agent` `maybe_active: Some(true)`). - **Scheduled run never fires** — check the skip reason events: `InsufficientAgentBalance` (top up the agent) or `PreviousRunInProgress` (set `allow_parallel` or shorten the work). --- ## skill: theseus-contracts --- name: theseus-contracts description: >- Write, compile, and deploy PolkaVM smart contracts on Theseus and drive them from on-chain agents. Contracts run under pallet_revive at EVM chain id 40999; Solidity compiles to PolkaVM bytecode with resolc 1.3.0. Covers the resolc/Foundry (pvm profile) and Hardhat setup with solc 0.8.24, storage deposits and PolkaVM size/memory limits, the deploy paths (direct via Revive.instantiate_with_code or forge create --resolc --use-resolc 1.3.0; agent-managed via contracts.deploy / contracts.upload_code), the precompile Solidity interfaces with their correct 20-byte addresses (Bridge, TheToken, ForeignAsset, Agents), and contract→agent callbacks (the Agents precompile IAgents.requestAgent, CallbackSpec, onAgentCallback). Use when the user wants to write/compile a Solidity/PolkaVM contract for Theseus, set up resolc/Foundry/Hardhat for chain id 40999, deploy (direct or agent-managed), call/read a deployed contract, use a precompile from Solidity, or wire a contract to an agent run with a callback. license: MIT compatibility: >- For PolkaVM/Solidity contract work on Theseus (pallet_revive 0.18.0, EVM chain id 40999, spec_version 100). Hands-on use needs the resolc Solidity→PolkaVM compiler 1.3.0 with solc 0.8.24, and Foundry (pvm profile) and/or Hardhat with @parity/hardhat-polkadot, plus a Theseus node + eth-rpc sidecar (or the hosted playground). Precompile addresses reuse the verified set in theseus-rpc. metadata: version: "0.2.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus Contracts — PolkaVM/Solidity + agent integration Theseus executes smart contracts under **`pallet_revive`** (which replaced `pallet_contracts`) and exposes an **Ethereum-compatible** surface at **EVM chain id 40999**. You write normal Solidity and compile it to **PolkaVM** bytecode with **`resolc` 1.3.0** (the Solidity→PolkaVM compiler), then deploy either through standard EVM tooling or, on Theseus, **agent-managed** via the `contracts.*` native tools. For RPC endpoints and the exact precompile address derivation, see **theseus-rpc**. For the native tools an agent uses to deploy/call, see **theseus-native-tools**. ## Toolchain: resolc 1.3.0 - **Compiler:** `resolc` **1.3.0** on top of **solc 0.8.24**. resolc lowers Solidity to PolkaVM bytecode (not EVM bytecode); the chain runs `AllowEVMBytecode = false`, so PolkaVM bytecode is required. - **EVM target version:** the contracts in `theseus-layerzero-evm` do not pin `evmVersion` in their Foundry/Hardhat config, so solc's default (Cancun for 0.8.24) applies — Cancun is the effective target. Pin `evmVersion = "cancun"` explicitly if your toolchain defaults differ. - **Storage deposits:** `pallet_revive` charges a refundable **storage deposit** per byte/item held in contract storage (`DepositPerByte`, `DepositPerItem`, child-trie deposits, and a code-hash lockup percentage). Deploys and state-growing calls must fund a `storage_deposit_limit`; it is returned when storage is freed. Code upload locks a deposit proportional to code size. - **Size / memory limits:** PolkaVM imposes runtime/PVF memory bounds (`RuntimeMemory`, `PVFMemory`) and a code-blob size limit. Large contracts may need splitting or `--legacy` (pre-EIP-1559) submission when EIP-1559 fee estimation rejects an oversized deploy. Keep contracts modular; prefer `upload_code` once + multiple instantiations from the same `code_hash`. ## Build: Foundry (pvm profile) The reference repo uses two Foundry profiles — an EVM `default` and a PolkaVM `pvm` profile that turns on resolc: ```toml [profile.default] solc-version = '0.8.24' optimizer_runs = 1000 [profile.default.polkadot] resolc_compile = false [profile.pvm] solc-version = '0.8.24' out = 'out-pvm' cache_path = 'cache/pvm' optimizer_runs = 1000 [profile.pvm.polkadot] resolc_compile = true ``` Build / deploy: ```bash # PolkaVM build → ./out-pvm FOUNDRY_PROFILE=pvm forge build # Direct deploy with resolc 1.3.0 forge create contracts/MyContract.sol:MyContract \ --rpc-url http://127.0.0.1:8545 \ --private-key \ --resolc --use-resolc 1.3.0 \ --constructor-args \ --broadcast [--legacy] ``` Use `--legacy` for large bytecode or when EIP-1559 submission fails for the deploy. (Foundry-polkadot is required for the `--resolc` / `polkadot` profile support.) ## Build: Hardhat (@parity/hardhat-polkadot) ```ts // hardhat.config.ts resolc: { version: '1.3.0', compilerSource: 'binary', settings: { optimizer: { enabled: true, parameters: '3' }, solcPath: process.env.HOME + '/.svm/0.8.24/solc-0.8.24', }, }, solidity: { compilers: [{ version: '0.8.24', settings: { optimizer: { enabled: true, runs: 200 } } }] }, networks: { 'theseus-devnet': { eid: 40999, url: process.env.RPC_URL_THESEUS || 'http://localhost:8545', accounts, polkadot: { target: 'pvm' }, }, }, ``` `resolc.settings.optimizer.parameters: '3'` is the PolkaVM optimisation level (distinct from solc's `runs`). `polkadot.target: 'pvm'` tells the plugin to compile with resolc for that network. Dep: `@parity/hardhat-polkadot` (≈0.2.7). ## Deploy paths | Path | How | When | | --- | --- | --- | | **Direct (EVM tooling)** | `forge create --resolc` / `cast send --create`, or `eth_sendRawTransaction` to :8545 | You hold an Ethereum key and deploy from off-chain. | | **Direct (Substrate)** | `Revive.instantiate_with_code` (upload+instantiate) or `Revive.instantiate` (from a `code_hash`) extrinsic | You drive the node from a Substrate signer. | | **Agent-managed** | `contracts.upload_code` then/or `contracts.deploy` native tools (from a SHIP agent) | An on-chain agent should own/deploy the contract. | Agent-managed deploy (see theseus-native-tools for full args): - **`contracts.upload_code(code, storage_deposit_limit?)`** → `{ code_hash }`. - **`contracts.deploy(...)`** — `code` (upload+instantiate) or `code_hash` (instantiate existing); `constructor`+`constructor_args` or pre-encoded `data`; optional 32-byte `salt` → CREATE2 (omit → CREATE1). Returns `{ address, code_hash }`. Queues a deploy effect that settles asynchronously. - **`contracts.address_of(...)`** predicts the address before deploying. ## Theseus precompiles from Solidity Cast a precompile's fixed address to its interface and call it. Addresses use the `0x…pppp0000` format (derivation + audit in **theseus-rpc**); the values below are the verified, corrected addresses. | Precompile | Address | | --- | --- | | Bridge | `0x0000000000000000000000000000000008020000` | | TheToken | `0x0000000000000000000000000000000008030000` | | ForeignAsset | `0x0000000000000000000000000000000008040000` | | Agents | `0x000000000000000000000000000000000A010000` | Solidity interfaces (from `pallets/precompiles/src/*.rs` and the contracts repo `interfaces/*.sol`): ```solidity // TheToken — native THE as ERC-20-ish; lock/unlock are TheOFT-only. interface ITheTokenPrecompile { function lockTokens(address from, uint256 amount) external; function unlockTokens(address to, uint256 amount) external; function balanceOf(address account) external view returns (uint256); function totalSupply() external view returns (uint256); function transfer(address from, address to, uint256 amount) external; } ITheTokenPrecompile constant THE = ITheTokenPrecompile(0x0000000000000000000000000000000008030000); // Bridge — LayerZero inbound/outbound + XC query (OApp-only callers). interface IBridgePrecompile { function receiveXCMessage(uint32 srcEid, bytes32 sender, uint64 nonce, bytes32 guid, bytes calldata message) external returns (bool hasCompose, address composeTo, bytes memory composeMsg); function notifySend(uint32 dstEid, address sender, bytes32 guid) external; function getXCQueryResult(uint64 queryId) external view returns (bool found, bytes memory result); function getReadChannelId() external view returns (uint32 channelId); } // ForeignAsset — bridged ERC-20s backed by pallet_assets. interface IForeignAssetPrecompile { function createForeignAsset(uint128 assetId, string calldata name, string calldata symbol, uint8 decimals) external; function mintForeign(uint128 assetId, address to, uint256 amount) external; function burnForeign(uint128 assetId, address from, uint256 amount) external; function foreignTransfer(uint128 assetId, address from, address to, uint256 amount) external; function foreignBalanceOf(uint128 assetId, address account) external view returns (uint256); function foreignTotalSupply(uint128 assetId) external view returns (uint256); function foreignAssetExists(uint128 assetId) external view returns (bool); function foreignApprove(uint128 assetId, address owner, address spender, uint256 amount) external; function foreignAllowance(uint128 assetId, address owner, address spender) external view returns (uint256); function foreignTransferFrom(uint128 assetId, address owner, address spender, address to, uint256 amount) external; function registerAssetERC20(uint128 assetId, address erc20) external; function getAssetIdForERC20(address erc20) external view returns (uint128); } ``` > **Caller checks.** Several precompile methods are permissioned: TheToken's > `lockTokens`/`unlockTokens`/`transfer` require the caller to be the configured > `TheOFT` address; the Bridge precompile's `receiveXCMessage`/`notifySend` > require the caller to be the configured `TheseusOApp`. They revert otherwise. > The view methods (`balanceOf`, `getXCQueryResult`, `foreignBalanceOf`, …) are > callable by anyone. ## Contract → agent callbacks (the Agents precompile) A contract can **request an agent run** and get a **callback** when the run finishes, via the Agents precompile at `0x…0A010000`: ```solidity interface IAgents { function requestAgent( bytes32 agent, // the agent's 32-byte AccountId bytes calldata input, // SCALE-encoded run input address callbackContract, // address(0) = no callback uint256 callbackValue, // value to forward on the callback uint64 callbackRefTime, // callback gas: ref_time uint64 callbackProofSize, // callback gas: proof_size uint128 callbackStorageDepositLimit, // 0 = none uint32 ttlBlocks // request expiry ) external returns (uint64 requestId, uint64 runId); } IAgents constant AGENTS = IAgents(0x000000000000000000000000000000000A010000); ``` `requestAgent` must be called in a **non-read-only** context (it reverts in a static call) and not from a Root origin. It enqueues a `ContractAgentRequest` into the target agent's inbox and returns `(requestId, runId)`. If `callbackContract != address(0)`, a `CallbackSpec { contract, value, gas_limit, storage_deposit_limit }` is recorded; when the run terminates the runtime fires an `Effect::Callback` that calls your contract back. The callback contract must implement: ```solidity function onAgentCallback( uint256 requestId, uint256 runId, bool success, bytes calldata output ) external payable; ``` Flow: ```mermaid sequenceDiagram participant C as Your contract participant P as Agents precompile (0x..0A010000) participant A as pallet_agents C->>P: requestAgent(agent, input, callbackContract, ...) P->>A: queue_contract_request -> (requestId, runId) A-->>C: (returns requestId, runId synchronously) Note over A: agent run executes over later block(s) A->>C: onAgentCallback(requestId, runId, success, output) ``` The request **expires** after `ttlBlocks` if the agent never runs it. Callbacks are outbound (agent→contract) and requests are inbound (contract→agent) on separate queues, so there is no deadlock between a contract and the agent it calls. ## End-to-end example: write → deploy agent-managed → call → callback 1. **Write** `Caller.sol` that, in some method, calls `AGENTS.requestAgent(agentId, abi.encode(...), address(this), 0, refTime, proofSize, 0, 50)` and implements `onAgentCallback(...)` to consume `output`. 2. **Compile** with resolc 1.3.0 (`FOUNDRY_PROFILE=pvm forge build`). 3. **Deploy agent-managed:** from your agent, `contracts.upload_code(code)` → `code_hash`, then `contracts.deploy({ code_hash, constructor, constructor_args })` → `{ address }`. (Or deploy directly with `forge create --resolc --use-resolc 1.3.0`.) 4. **Drive it:** the agent calls the contract method with `contracts.call({ address, function: "kickoff(...)", args: [...] })`. The contract calls `requestAgent` on the Agents precompile, which queues a run on the target agent. 5. **Callback:** when that run finishes, `pallet_agents` calls `onAgentCallback(requestId, runId, success, output)` on the contract, closing the loop. For reading contract state without an agent, use the eth-rpc `eth_call` (theseus-rpc); from an agent, `contracts.call_view` (with the caveat in theseus-native-tools that it can commit state for non-view functions). --- ## skill: theseus-native-tools --- name: theseus-native-tools description: >- Reference for the Theseus on-chain native tool surface — the in-runtime tools a SHIP agent calls directly, by namespace: agent.* (set_metadata), tokens.* (list/balance/info/get_allowance/transfer/approve), chain.* (evm_address/balance/transfer of THE), abi.* (encode/decode), capabilities.* (list/describe/search — always available), contracts.* (call/call_view/deploy/upload_code/address_of/code_at/storage_at), bridge.* (query/send_token/send_the/send_message/deploy_ica/get_ica), and agents.* (call/get_info/get_inbox — agent-to-agent). Documents the dispatch model (inline vs queued-sync vs queued-async effects), the resource preflight/blocking model, the tools.yaml native-tools allowlist, the ToolCall shape, and how bridge.* availability is gated by the T::BridgeCaller trait bound ("bridge not available" if unwired) and discoverable via capabilities.list/describe. Use when the user asks what an on-chain agent can call, how to transfer THE or a token, read a balance/allowance, deploy/call a contract, ABI-encode/decode, call or message another agent (A2A), or which native tool to put in tools.yaml. license: MIT compatibility: >- Reference for the Theseus native tool surface exposed by pallet_agents on theseus-chain spec_version 100 (pallet-revive 0.18.0). Tool availability tracks the runtime; bridge.* requires a T::BridgeCaller implementation (the production runtime wires one). No special local tooling required to read; exercising tools needs a deployed agent on a Theseus node or the hosted playground. metadata: version: "0.3.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus Native Tools — the on-chain tool surface Native tools are dispatched **in the runtime**, in-process, with no off-chain executor (`pallet_agents/src/native_tools.rs`). An agent calls them by name; the runtime recognises a name as native via `is_native_tool`. There are 30 native tools across eight namespaces. This page covers the dispatch model, the allowlist, and a compact catalog. For the exhaustive per-tool argument/return tables, see [references/tool-catalog.md](references/tool-catalog.md). For precompile addresses and the RPC surface, see **theseus-rpc**; for writing the contracts these tools deploy/call, see **theseus-contracts**. ## Namespaces at a glance - **`agent.*`** — self-management: `set_metadata`. - **`tokens.*`** — fungible assets held by the agent: `list`, `balance`, `info`, `get_allowance`, `transfer`, `approve`. - **`chain.*`** — native THE + identity: `evm_address`, `balance`, `transfer`. - **`abi.*`** — calldata helpers: `encode`, `decode`. - **`capabilities.*`** — runtime discovery, **always available** even with an empty allowlist: `list`, `describe`, `search`. - **`contracts.*`** — PolkaVM contracts (`pallet_revive`): `call`, `call_view`, `deploy`, `upload_code`, `address_of`, `code_at`, `storage_at`. - **`bridge.*`** — cross-chain (LayerZero / interchain accounts): `query`, `send_token`, `send_the`, `send_message`, `deploy_ica`, `get_ica`. Gated by the bridge trait (see below). - **`agents.*`** — agent-to-agent (A2A): `call` (message another agent, with an optional `wait` to block for its reply), `get_info` (a target's Agent Card), `get_inbox` (own pending messages). ## Dispatch model: inline vs queued effects Every native tool is one of three execution shapes: | Class | What happens | Settles | Examples | | --- | --- | --- | --- | | **Inline** | Resolves synchronously in the dispatcher; the result is staged immediately. No `EffectsQueue` entry. | Same call | `abi.encode/decode`, `tokens.info`, `tokens.balance` (read), `chain.evm_address`, `contracts.call_view`, `contracts.address_of/code_at/storage_at`, `bridge.get_ica`, `agents.get_info`, `agents.get_inbox`, `capabilities.*` | | **Queued, sync** | Enqueues an `Effect`; drained and settled **in the same block** via `drain_effects`. | This block | `tokens.transfer`, `tokens.approve`, `chain.transfer` | | **Queued, async** | Enqueues an `Effect`; the outcome arrives in a **later block** (contract execution / bridge round-trip) and resumes the run. | Later block | `contracts.call`, `contracts.deploy`, `contracts.upload_code`, all writing `bridge.*` (`query`, `send_*`, `deploy_ica`), `agents.call` (`wait=true` resumes when the target completes) | A queued tool emits `Effect.Queued` at dispatch and `Effect.Settled` on outcome (both carry the `tool_call_id`). An inline tool emits only `Effect.Settled` with `details: Inline`. See `docs/agent-tool-events.md` for the event lifecycle. ### Resource preflight / blocking Before dispatch, a batch of native calls is checked by `preflight_blocking_batch` against in-flight effects on the same **resource**: - `OwnBalance` — the agent's native THE balance. - `OwnTokenBalance(asset_id)` — a specific fungible's balance. - `ContractStorage(account)` — a contract's state. If a call would conflict with a concurrent run's in-flight effect on the same resource, the **whole batch** is re-queued and the run pauses with `PauseReason::WaitingForResource`; it resumes when the resource frees. This is how Theseus serialises balance/state mutations deterministically. Pure reads of *other* accounts don't block; reads/writes of the agent's own balance do. ## `tools.yaml` and the native allowlist The agent's `tools.yaml` `native-tools` block becomes its `native_allowlist`: - **block absent** → all natives allowed (the default, `native_allowlist = None`); - **present, non-empty** → only those names are callable; - **present, empty `[]`** → no natives — **except** `capabilities.*`, which is always available so the agent can still introspect what it has. On every `ModelInvoke` the runtime intersects the model's offered tools with the allowed native subset. (The `common-tools` and `byo-tools` blocks are separate; see theseus-agents.) ## Invocation shape (`runtime.call_tool` / ToolCall) A tool call is a `ToolCall { call_id, tool_name, arguments }` where `arguments` is a JSON object (SCALE-bounded). In normal operation the **model** emits tool calls during a `ModelInvoke`, and a `Tool`-role node's `ToolsDispatch` executes them; the runtime partitions native vs external calls, runs preflight, then dispatches natives in-process and routes external calls to `pallet_tools`. Results are flushed back in the original `call_id` order (not arrival order). Authors can also invoke many of these effects deterministically from `Code` nodes via the typed `Action` variants (`ChainTransfer`, `TokenTransfer`, `ContractsCall`, …) — see the graph-model reference in theseus-agents. Argument conventions across tools: - Amounts/asset ids are **strings** carrying raw integers (planck for THE; raw units for tokens). `"self"` means the agent's own account; SS58 strings and `0x` H160 strings are accepted where an account/address is expected. - Contract tools accept either `function` + `args` (Solidity-style, ABI-encoded for you) **or** a pre-encoded `calldata`/`data` hex string. ## Compact catalog Full args/returns in [references/tool-catalog.md](references/tool-catalog.md). | Tool | Class | One-liner | | --- | --- | --- | | `agent.set_metadata` | inline | Replace own description/avatar_uri/tags. | | `tokens.list` | inline | Held assets with non-zero balances. | | `tokens.balance` | inline | Balance of one asset (`self` or address). | | `tokens.info` | inline | name/symbol/decimals/total_supply. | | `tokens.get_allowance` | inline | Allowance owner→spender for an asset. | | `tokens.transfer` | queued-sync | Transfer an asset to an account. | | `tokens.approve` | queued-sync | Approve a spender for an asset. | | `chain.evm_address` | inline | The agent's own H160. | | `chain.balance` | inline | Native THE balance (planck). | | `chain.transfer` | queued-sync | Send native THE. | | `abi.encode` | inline | ABI-encode `fn(sig)` + args → calldata. | | `abi.decode` | inline | Decode hex by a type signature. | | `contracts.call` | queued-async | Invoke a deployed contract (state-changing). | | `contracts.call_view` | inline | `bare_call` read (no commit; see caveat). | | `contracts.deploy` | queued-async | Upload+instantiate or instantiate by code_hash. | | `contracts.upload_code` | queued-async | Upload PolkaVM bytecode → code_hash. | | `contracts.address_of` | inline | Predict a CREATE1/CREATE2 address. | | `contracts.code_at` | inline | Read deployed bytecode at an H160. | | `contracts.storage_at` | inline | Read a 32-byte storage slot. | | `bridge.query` | queued-async | lzRead a view fn on a remote chain. | | `bridge.send_token` | queued-async | Bridge a foreign asset (+ optional ICA calls). | | `bridge.send_the` | queued-async | Bridge native THE (+ slippage, ICA calls). | | `bridge.send_message` | queued-async | Cross-chain message (typed ICA calls). | | `bridge.deploy_ica` | queued-async | Deploy an interchain account on a remote chain. | | `bridge.get_ica` | inline | The agent's ICA address + deployed status. | | `agents.call` | queued-async | Message another agent; optional `wait` blocks for its reply. | | `agents.get_info` | inline | Another agent's Agent Card (name, mode, capabilities, schema). | | `agents.get_inbox` | inline | Own pending inbound messages (task_id, source, queued_at). | > **`contracts.call_view` caveat:** it runs `pallet_revive::bare_call`, which can > commit state for a non-view function. For strict EVM read semantics use the > eth-rpc `eth_call` path (see theseus-rpc) instead of `call_view` on > state-mutating functions. ## `capabilities.*` — discovery & progressive disclosure - **`capabilities.list`** → every native tool, external tool (BYO/common), and skill on the agent, each tagged `class: native|byo|common|skill`. Inactive common tools appear with `status: "unavailable"`. - **`capabilities.describe(name)`** → full schema for one tool (and, for the rest of the run, injects that tool's schema into subsequent model invocations); for a **skill**, it *activates* the skill (its instructions + `allowed-tools` enter the effective set) and emits `SkillActivated`. - **`capabilities.search(query)`** → keyword-ranked capabilities. This is the mechanism behind progressive disclosure: an agent starts with a small tool set, `search`/`list` to find more, and `describe` to load a tool's schema or activate a skill on demand. ## `agents.*` — agent-to-agent (A2A) calls Agents message each other on-chain. **`agents.call(to, message, value?, wait?)`** delivers `message` (a structured object) into the target agent's inbox and starts a run on it: - **`wait: false`** (default) — fire-and-forget; the call settles immediately with a `task_id` and the caller never receives a reply. - **`wait: true`** — the caller's run pauses until the target run completes, and the target's final answer is returned as this tool's result (await-and-resume, the same machinery as `bridge.query`). `value` (optional, planck string) is a budget the caller grants the target run: for a `Managed` target it is a caller-funded escrow (drawn down by the target's metered work, unused remainder refunded to the caller); a self-funded (autonomous/sovereign) target receives it as a plain transfer and funds its own run. Conversation chains are depth-bounded by `Config::MaxConversationHops`, and a call may not re-enter an agent already waiting earlier in the same chain (a reentrancy guard over the `wait=true` call stack). The two reads are inline pure-discovery — use them before calling: - **`agents.get_info(agent_id)`** → the target's Agent Card: `name`, `description`, `mode`, `active`, `version`, `protocolVersion`, declared `capabilities` (tool names), and `inputSchema`. - **`agents.get_inbox()`** → the calling agent's own pending inbound messages (`task_id`, `source` `{agent|contract}`, `queued_at`). `agents.call` is **not** baseline (unlike `capabilities.*`): like any other non-`capabilities.*` native it must be in the effective set to dispatch — either allowlisted in `tools.yaml`, or activated at runtime by `capabilities.describe` before the model calls it. ## Bridge gating — the injectable active/inactive model All cross-chain functionality (`bridge.*` and `chain.evm_address`) is gated by the **`T::BridgeCaller` associated type** on the agents pallet's `Config` (`pallet_agents::Config::BridgeCaller`): - The pallet ships a **default no-op** `BridgeCaller` whose dispatch methods return `DispatchError::Other("bridge not available")`. A runtime that wires *that* leaves bridge tools present-but-failing. - The **Theseus production runtime injects a real implementation** — `type BridgeCaller = BridgeCallerAdapter` in `runtime/src/configs/mod.rs` — so on the live chain the bridge tools are **active** (backed by `pallet_bridge` over LayerZero V2 + interchain accounts). Either way, the tools are always *present* in the registry and discoverable: - `capabilities.list` shows every `bridge.*` tool with `class: "native"` regardless of whether a bridge is wired. - A `bridge.*` call on a runtime without an implementation returns the tool error `"bridge not available"` at execution time. So "is bridging available?" is a **runtime injection** question, not a compile-time feature flag, and an agent can probe it at runtime: call a cheap inline one like `bridge.get_ica` (or `chain.evm_address`) and treat a `"bridge not available"` error as "this deployment has no bridge wired". The bridge model is intentionally typed: an agent expresses intent via high-level args (`dst_eid`, `recipient`, `asset_id`/`amount`, typed `calls`, optional `gas` overrides) and never touches raw LayerZero option blobs — the bridge dispatcher handles encoding, fee quoting, sending, and pending-receipt registration. `receipt_requested: true` requires non-empty `calls` and that the recipient be the agent's own ICA on the destination chain. --- ## skill: theseus-network --- name: theseus-network description: >- Orientation and router skill for the Theseus network — a Substrate/PolkaVM app-chain for autonomous on-chain AI agents (first-class chain accounts that run a SHIP agent graph, call native tools, deploy PolkaVM contracts, hold balances, and wake on heartbeats). Explains the core building blocks (the agents pallet, SHIP CompiledAgent format, the native tool surface, pallet_revive contracts, EVM chain id 40999, SS58 + H160 addressing) and routes to the right companion skill. Use this skill FIRST when a user mentions Theseus, theseus-chain, the Theseus playground, SHIP agents, on-chain agents, register_ship_agent, native tools, or precompiles, or asks how Theseus works / where to start — then route to theseus-agents (author/deploy agents), theseus-native-tools (the tool surface), theseus-rpc (JSON-RPC, accounts, precompile addresses), theseus-contracts (PolkaVM/Solidity via resolc), or theseus-playground (browser IDE). Use whenever you need a map of the skillset. license: MIT compatibility: >- Reference/orientation skill for the Theseus app-chain (Substrate + PolkaVM via pallet_revive 0.18.0). Works in any agent client. Hands-on tasks may need the Theseus toolchain (Rust, the agent-compiler, resolc 1.3.0) and a Theseus node or the hosted playground; see the companion skills for those requirements. metadata: version: "0.2.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus Network — overview and router ## What Theseus is Theseus is a Substrate-based L1 app-chain whose headline primitive is the **on-chain AI agent**. An agent is a real chain account that: - owns a balance (native **THE** and fungible assets), - runs a deterministic **SHIP** agent graph (`pallet_agents`), - calls **native tools** in-runtime (read balances, transfer, deploy/call contracts, ABI-encode, bridge cross-chain, introspect its own capabilities), - can deploy and call **PolkaVM smart contracts** (`pallet_revive`), and - can wake on **heartbeats** (scheduled runs) when its sovereignty mode allows. PolkaVM contracts run under `pallet_revive` with an Ethereum-compatible surface: **EVM chain id 40999**. Every account therefore has both a Substrate **SS58** identity and an Ethereum **H160** identity (see `theseus-rpc` for the mapping). ## Core building blocks (one line each) - **agents pallet (`pallet_agents`)** — registration (`register_ship_agent`), updates (`update_agent`), invocation (`call_agent`), runs, heartbeats, metadata, sovereignty modes, the native tool dispatcher. - **SHIP / `CompiledAgent`** — the compiled agent format: `THESEUS.md` (frontmatter + system prompt), an `agent.rs` node/action/terminator graph, `tools.yaml`, and `skills/*/SKILL.md`. The agent-compiler SCALE-encodes it. - **native tool surface** — in-runtime tools agents call by name: `agent.*`, `tokens.*`, `chain.*`, `abi.*`, `capabilities.*`, `contracts.*`, and `bridge.*`. Each is inline, queued-sync, or queued-async. - **`pallet_revive` contracts** — Solidity compiled to PolkaVM bytecode with **resolc 1.3.0**; deployed via standard EVM tooling or agent-managed (`contracts.deploy`). Custom precompiles expose chain features to Solidity. - **playground** — a browser IDE that compiles agents in-browser (WASM) and deploys them with one click against a local node or the hosted testnet. ## Mental model ```mermaid flowchart LR A[Author: THESEUS.md + agent.rs + tools.yaml + skills/] -->|agent-compiler| B[SCALE-encoded CompiledAgent] B -->|register_ship_agent| C[Agent account: SS58 + H160] C -->|call_agent / heartbeat / contract request| D[Run: walk the node graph] D -->|ModelInvoke| E[LLM decides tool calls] E -->|native tools| F[tokens / chain / contracts / abi / bridge / capabilities] E -->|external tools| G[common / byo executors] F -->|effects| H[balances, pallet_revive, bridge, assets] ``` ## Glossary - **agent** — an on-chain account backed by a `CompiledAgent` (an `AgentInfo` record in storage). - **run** — one execution of an agent's graph, started by an extrinsic, a heartbeat, an agent-to-agent call, or a contract request. - **node / action / terminator** — the graph: a `Node` has a role (`Model`/`Tool`/`Code`), a list of `actions`, and a `terminator` (`Goto`/`If`/`End`). - **native tool** — a tool the runtime executes in-process (no off-chain executor), e.g. `tokens.transfer`. - **effect** — a queued on-chain action a tool produces (transfer, deploy, call, bridge) that settles in the same or a later block. - **heartbeat** — a scheduled run on an `interval_blocks` cadence (Autonomous / Sovereign agents only). - **sovereignty mode** — `Managed` / `Autonomous` / `Sovereign`: who owns the agent and who funds its runs. ## Network facts - **Chain id:** `40999` (EVM) — `TheseusChainId` in the runtime. - **Contracts pallet:** `pallet_revive` 0.18.0 (PolkaVM; replaced `pallet_contracts`). `AllowEVMBytecode = false` (PolkaVM bytecode only). - **Address mapping:** `pallet_revive::AccountId32Mapper` (SS58 ↔ H160). - **Substrate RPC (dev):** `ws://127.0.0.1:9944`. - **Ethereum JSON-RPC (dev):** `http://127.0.0.1:8545` (the separate `pallet-revive-eth-rpc` sidecar). - **Native token:** THE. - **Toolchain tuple:** sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0. ## Routing — pick the next skill | Your task | Skill | | --- | --- | | Author, compile, deploy, or operate an agent (THESEUS.md, agent.rs, tools.yaml, sovereignty, heartbeats) | **theseus-agents** | | What an agent can call on-chain — every native tool, its args/returns, inline vs queued effects, bridge gating | **theseus-native-tools** | | JSON-RPC endpoints, custom runtime APIs, SS58↔H160 accounts, precompile addresses, chain id 40999 | **theseus-rpc** | | Write/compile/deploy PolkaVM (Solidity-via-resolc) contracts, precompile Solidity interfaces, contract→agent callbacks | **theseus-contracts** | | Use the browser IDE: workspaces, in-browser compile, the Deploy button, local vs testnet, prompting agents | **theseus-playground** | If a question spans several subsystems, read this router first, then the most specific skill. The skills are written to not overlap: native-tool *signatures* live in `theseus-native-tools`; precompile *addresses and RPC* live in `theseus-rpc`; Solidity *contract authoring* lives in `theseus-contracts`. --- ## skill: theseus-playground --- name: theseus-playground description: >- Guide to the Theseus playground — the browser IDE (Monaco + in-browser WASM agent-compiler) for building and shipping on-chain agents — and how Claude-for-Chrome drives it. Explains workspaces (browser-persisted projects holding the reserved files THESEUS.md, agent.rs, tools.yaml, skills/*/SKILL.md), in-browser compile with inline Monaco diagnostics, and the Deploy flow (compile → hash → submit register_ship_agent with endowment + sovereignty → finalize → read the Registered event → optional set_agent_metadata) with status states idle/compiling/signing/in-block/ finalized/error. Covers local vs testnet modes (local dev node at ws://localhost:9944 with dev accounts; testnet hosted alpha with sign-in + faucet), prompting agents via call_agent, the FileTree/NetworkPanel/AgentRow UI, what the UI does NOT expose, and concrete click-paths. Use when the user asks how to use the playground, manage workspaces, compile in the browser, Deploy, fix compile errors, choose local vs testnet, or automate it. license: MIT compatibility: >- Describes the Theseus playground browser IDE (Next.js 16 App Router, React 19, Monaco, in-browser WASM agent-compiler, PAPI chain client). Targets theseus-chain spec_version 100. Local mode expects a Theseus dev node at ws://localhost:9944 (+ eth-rpc / indexer / explorer); testnet uses the hosted alpha. No local Rust toolchain needed — compilation runs in the browser. metadata: version: "0.2.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus Playground — browser IDE for agents The playground is a browser IDE (Monaco editor + a WASM build of the chain's agent-compiler) for authoring a Theseus agent, compiling it **in the browser**, and deploying it to the chain — no local Rust toolchain required. It signs and submits with PAPI (polkadot-api). This skill documents the UI and the exact click-paths for driving it with Claude-for-Chrome. For the agent file formats see **theseus-agents**; for what the deployed agent can call see **theseus-native-tools**; for endpoints/accounts see **theseus-rpc**. ## Layout Three panes: a **file tree** (left), the **Monaco editor** (center), and a **network panel** (right) that holds connection status, the account, the deploy controls, and "Your agents". ## Workspaces and reserved files Each **workspace** is a self-contained project persisted in the browser (IndexedDB via localforage). A starter **hello-agent** workspace is created on first load. The workspace identity contributes to the deploy **salt** (and thus the agent address); resetting identity mints a fresh one while keeping the code. **Reserved files** (the compiler requires them; the file tree marks them "Core file — required by the compiler"): - `THESEUS.md` — frontmatter + system prompt. - `agent.rs` — builds the `CompiledAgent`. - `tools.yaml` — the tool surface. - `skills//SKILL.md` — auto-discovered skills (the `skills/` folder is "required by the compiler"; renaming a skill folder changes its skill id). The starter workspace ships `THESEUS.md`, `agent.rs`, `skills/check-balance/SKILL.md`, and `tools.yaml`. You can add arbitrary additional files. ## In-browser compile Editing triggers compilation through the WASM agent-compiler, producing SCALE bytes and **inline diagnostics** (errors/warnings) in Monaco. Skills are discovered from `skills/*/SKILL.md` and sorted for deterministic output. A compile error blocks deploy and surfaces as a **"Compile error"** callout in the network panel. ## The Deploy flow and status states Deploy runs: **compile → hash the SCALE bytes → sign and submit `register_ship_agent(mode, value, payload, salt)` → watch for finalization → parse the `Registered` event** for the agent account (SS58 + the `evm_address` H160) → optionally submit `set_agent_metadata`. The deploy status state machine uses these exact strings: `idle` → `compiling` → `signing` → `in-block` → `finalized` (or `error`). The **endowment** (the `value` sent to the new agent account) is set in the deploy controls ("Endowment"), and the sovereignty mode comes from the `THESEUS.md` `sovereignty` field. The deploy CTA is context-sensitive: - **"Deploy this workspace"** — nothing deployed yet for this workspace. - **"Deploy update"** — the workspace changed since the last deploy. - **"Up to date"** — current code matches the deployed agent (clicking opens the explorer); a **"Deploy as fresh agent"** link mints a new agent from the same code (new salt/identity). While deploying, the panel shows "Compiling…", "Signing…", "In block…", then an "Agent registered" callout on success. ## Local vs testnet | Mode | Connects to | Accounts | Notes | | --- | --- | --- | --- | | **local** | dev node `ws://localhost:9944` | built-in dev accounts (Alice/Bob/…) selectable in the panel | for day-to-day dev against the local stack (node + eth-rpc + indexer + explorer). | | **testnet** | the hosted alpha endpoint | sign-in (Supabase Auth) + a **faucet** to fund your account | for public testing/demos. | The network panel shows the mode, **"RPC URL"**, **"Current Block"**, and the **Account** block with **"SS58"**, **"EVM"**, and **"Balance"** rows (a guest shows "Guest"). Local mode also surfaces chain-startup states like "Waiting for chain to author block 1…", and an "Indexer offline · showing local cache" notice when the indexer is down. ## After deploy: prompting an agent Deployed agents appear under **"Your agents"** as rows (`AgentRow`). Each row has **"Prompt"** and **"Watch on explorer"** controls, an "Ask the agent…" textarea (submit with Cmd/Ctrl+Enter, button **"Send"**), and an Active/Retired status dot. Sending a prompt submits **`call_agent`** with your text as the run input; the status line runs "Signing…" → "In block…" → "Finalized · opening explorer", then opens the explorer to watch the run. ## What the playground does NOT expose - No raw extrinsic console — only `register_ship_agent`, `set_agent_metadata`, and `call_agent` are wired through the UI. - No in-browser contract (Solidity/resolc) compiler — the WASM compiler is for *agents* only; for contracts use the toolchain in **theseus-contracts**. - No arbitrary key import UI in local mode (it uses the chain's dev accounts); testnet accounts come from sign-in, funded via the faucet. - No low-level chain debugging tools; observability is via the linked explorer. ## Running it locally Requires bun and a Theseus node at `ws://localhost:9944`. `bun install && bun run dev` serves it at `http://localhost:3000`. The backing services in local mode are the node (9944), the eth-rpc sidecar (8545), the indexer (Postgres) behind the `/api/agents` route, and the explorer. ## Claude-for-Chrome click-paths These are the concrete steps for driving the playground with the Claude-in-Chrome tools (`navigate`, `read_page`/`get_page_text`, `find`, `computer`/`left_click`, `form_input`/`type`, `read_console_messages`). Monaco is a canvas-ish editor, so prefer reading via the page text / accessibility tree and typing into the focused editor. **Open and read the current agent** 1. `navigate` to the playground URL (`http://localhost:3000` locally, or the hosted testnet URL). 2. `read_page` / `get_page_text` to capture the file tree ("Files"), the editor contents, and the network panel (mode, "RPC URL", "Current Block", Account "SS58"/"EVM"/"Balance"). 3. In the file tree, click a reserved file (`THESEUS.md`, `agent.rs`, `tools.yaml`) to load it into Monaco; read it back from the page text. **Create / edit a file** 4. To create: in the file tree click **"New file"** (or the root context menu **"New File…"**), type the path (e.g. `skills/greeter/SKILL.md`), confirm. 5. To edit: click the file, click into the Monaco editor to focus it, then `type` the new content (select-all + replace if rewriting). Editing auto-triggers compile. **Set configuration** 6. Edit `THESEUS.md` frontmatter for identity/model/`sovereignty`; the sovereignty value drives the deploy mode. 7. In the network panel, set the **"Endowment"** field; in **local** mode pick a dev **Account** from the selector; in **testnet** mode sign in and click the **faucet** to fund the account first. **Deploy and read status** 8. Confirm there is no **"Compile error"** callout (read the panel text; if present, open the editor and fix the diagnostics, then recompile). 9. Click the deploy CTA — **"Deploy this workspace"** (or **"Deploy update"**). 10. Poll the panel text for the status: `compiling` → `signing` → `in-block` → `finalized` ("Agent registered"), or `error`. On error, read the callout and `read_console_messages` for detail. Capture the new agent's SS58 + EVM address from the "Agent registered" callout / "Your agents" row. **Prompt the deployed agent** 11. In **"Your agents"**, find the row, click **"Prompt"**, type into the "Ask the agent…" textarea, and click **"Send"** (or Cmd/Ctrl+Enter). 12. Read the status line ("Signing…" → "In block…" → "Finalized · opening explorer"); use **"Watch on explorer"** to follow the run. Tips: the deploy and prompt flows pop a wallet/PAPI signing step — in local mode this signs with the selected dev account automatically; on testnet it uses the signed-in session. If "Current Block" is not advancing or shows "Waiting for chain to author block 1…", the local node isn't producing blocks yet — wait or restart `start-devnet.sh`. --- ## skill: theseus-rpc --- name: theseus-rpc description: >- Reference for talking to a Theseus node — the JSON-RPC surface, the dual SS58/H160 account model, and the on-chain precompile addresses. Covers the Substrate JSON-RPC (system/chain/state/author plus the custom runtime APIs AgentsApi.get_agent and ModelsApi.get_model), the separate Ethereum JSON-RPC (the pallet-revive-eth-rpc sidecar on :8545, eth_call/eth_sendRawTransaction/ eth_getBalance), how SS58 accounts and H160 addresses map via pallet_revive::AccountId32Mapper (0xEE-suffixed fallback + registered mappings) and which RPC path each uses, the EVM chain id 40999, and the canonical 20-byte precompile address format with the four custom precompiles (Bridge, TheToken, ForeignAsset, Agents) plus the standard EVM precompiles pallet-revive 0.18.0 actually ships. Use when the user asks how to connect over RPC, which endpoint/method to use, how SS58 and H160 relate, how to map/fund an account, a precompile's address, the chain id, or whether a standard precompile (ecrecover, sha256, bn128, KZG) exists. license: MIT compatibility: >- Reference for the Theseus node RPC surface and account/precompile model (Substrate + pallet_revive 0.18.0, EVM chain id 40999, spec_version 100). The Ethereum JSON-RPC requires the separate pallet-revive-eth-rpc sidecar binary (built from polkadot-sdk umbrella 2604.2.0). Addresses/methods verified against the runtime and pallet-revive 0.18.0 source. metadata: version: "0.2.0" theseus-spec-version: "100" theseus-polkavm-tuple: "sdk 2604.2.0 / pallet-revive 0.18.0 / resolc 1.3.0" --- # Theseus RPC — endpoints, accounts, precompiles Theseus is a Substrate chain with a PolkaVM contracts layer (`pallet_revive`). That gives it **two** RPC faces and **two** ways to name an account. This skill is the source of truth for endpoints, the SS58↔H160 mapping, and precompile addresses. (For the tools an *agent* calls, see theseus-native-tools; for writing contracts against the precompiles, see theseus-contracts.) ## Endpoints | Surface | Default (dev) | What it is | | --- | --- | --- | | **Substrate JSON-RPC** | `ws://127.0.0.1:9944` | The node's native RPC (WS/HTTP). System, chain, state, author, plus the custom runtime APIs below. Used by PAPI / polkadot.js / subxt. | | **Ethereum JSON-RPC** | `http://127.0.0.1:8545` | The `pallet-revive-eth-rpc` **sidecar** — a separate binary that proxies Ethereum JSON-RPC onto the node. Used by cast / ethers / viem / Foundry / Hardhat. | The devnet brings both up: `deploy/start-devnet.sh` runs `theseus-node --dev` (WS 9944) and then `eth-rpc --dev` (HTTP 8545). The eth-rpc binary is built separately (`deploy/build-eth-rpc.sh`) from polkadot-sdk umbrella 2604.2.0 (pallet-revive 0.18.0) and connects to the node's `ws://127.0.0.1:9944`. There is no Ethereum RPC without that sidecar running. **Chain id: `40999`** (`TheseusChainId` in `runtime/src/configs/mod.rs`). Use it for EIP-155 signing and as the network/endpoint id in Foundry/Hardhat. ## Substrate JSON-RPC Standard namespaces are available (`system_*`, `chain_*`, `state_*`, `author_*`, `payment_*`). Submit extrinsics like `register_ship_agent`, `call_agent`, and `set_agent_metadata` through `author_submitExtrinsic` (or a PAPI/polkadot.js signer). Two **custom runtime APIs** are declared in `runtime/src/runtime_api.rs`: | Runtime API | Method | Returns | | --- | --- | --- | | `AgentsApi` | `get_agent(agent_id: AccountId)` | `Option` — the full on-chain agent record (owner, mode, name, version, tools, schedule, description, tags, …). | | `ModelsApi` | `get_model(model_id: ModelId)` | `Option` — a registered model's record. | These are runtime-API calls (invoke via `state_call` with the `AgentsApi_get_agent` / `ModelsApi_get_model` method name and SCALE-encoded args, or via a metadata-aware client like subxt/PAPI). They are the canonical way to read agent/model state without scraping storage. ## The account model: SS58 ↔ H160 `pallet_revive` maps between 32-byte Substrate accounts and 20-byte Ethereum addresses with `AccountId32Mapper` (`type AddressMapper = pallet_revive::AccountId32Mapper` in the runtime). The rules (`substrate/frame/revive/src/address.rs`): - **Ethereum-derived accounts** carry a `0xEE` marker. An account is "eth-derived" iff its **last 12 bytes are all `0xEE`**. For such an account, `to_address` simply **strips the `0xEE` suffix** → the original H160. Equivalently, an Ethereum key's 32-byte account is `H160 ‖ [0xEE; 12]`. - **`to_account_id(h160)`** looks up a **registered** mapping (`OriginalAccount[h160]`); if none exists it falls back to the `0xEE`-suffixed account (`H160 ‖ [0xEE;12]`). - **Substrate-native accounts** (sr25519/ed25519 — a normal SS58 key whose bytes are *not* `0xEE`-suffixed) do **not** have a trivially derivable H160. To get a stable Ethereum address for such an account you **register a mapping** on `pallet_revive` (the map-account extrinsic), after which `to_address`/`to_account_id` round-trip through `OriginalAccount`. ### Which RPC path does each account type use? | You have… | Sign / query via | Notes | | --- | --- | --- | | An **Ethereum key** (secp256k1, an `0x` H160) | **Ethereum JSON-RPC** (`eth_sendRawTransaction`, EIP-155 with chain id 40999) | Its Substrate account is `H160 ‖ [0xEE;12]`; fund it as that SS58 account. | | A **Substrate key** (sr25519/ed25519 SS58) | **Substrate JSON-RPC** (signed extrinsics) | To also drive contracts as an H160, register an address mapping first. | | An **agent account** | Substrate side for state (`AgentsApi.get_agent`); its H160 is in the `Registered` event's `evm_address` and from `chain.evm_address` | Agents are first-class accounts with both identities. | Practical consequence: a contract deployed by an Ethereum key, and the same key's "native" balance, live under the `0xEE`-suffixed Substrate account — so `eth_getBalance(0xKEY)` and the Substrate balance of `0xKEY‖0xEE…` are the same balance. A pure SS58 dev account (Alice/Bob) needs a registered mapping before it has a meaningful, stable H160 for EVM calls. ## Precompiles ### Address format (verified against pallet-revive 0.18.0) External (chain-supplied) precompiles use `AddressMatcher::Fixed(p: u16)`. The pallet builds the 20-byte address by taking `p`, **left-shifting it 16 bits** into a `u32`, and writing those 4 big-endian bytes into `address[16..20]` (`precompiles.rs` `into_builtin` + `base_address`). All other bytes are zero. So the canonical format is: ```text 0x + (24 hex zeros) + (8-hex of (p << 16), big-endian) = 0x00000000000000000000000000000000 pppp 0000 ``` i.e. `0x` + 32 hex zeros, then the 4-hex `pppp`, then `0000`. Worked: for `p = 0x0802`, `(p << 16) = 0x08020000`, big-endian bytes `08 02 00 00` → `0x0000000000000000000000000000000008020000`. For `p = 0x0A01`, `(p << 16) = 0x0A010000` → `0x000000000000000000000000000000000A010000`. ### The four custom Theseus precompiles Registered in the runtime's `pallet_revive::Config::Precompiles` tuple (`configs/mod.rs`); matcher values read from `pallets/precompiles/src/*.rs`. | Precompile | `MATCHER` (u16) | Address | | --- | --- | --- | | **Bridge** (`BridgePrecompile`) | `0x0802` | `0x0000000000000000000000000000000008020000` | | **TheToken** (`TheTokenPrecompile`) | `0x0803` | `0x0000000000000000000000000000000008030000` | | **ForeignAsset** (`ForeignAssetPrecompile`) | `0x0804` | `0x0000000000000000000000000000000008040000` | | **Agents** (`AgentsPrecompile`) | `0x0A01` | `0x000000000000000000000000000000000A010000` | > **Corrections vs. earlier briefs/source comments.** (1) Some material paired > "Bridge" with `0x0803` and "TheToken" with `0x0802`; the source is the > opposite — **Bridge = 0x0802, TheToken = 0x0803** (and ForeignAsset = 0x0804). > (2) The doc-comment above `AgentsPrecompile::MATCHER` in `agents.rs` reads > `0x0000000000000000000000000000000000A010000`, which is **malformed** (the > `A01` is at the wrong offset and the string is the wrong length). The correct > address is `0x000000000000000000000000000000000A010000`. Solidity interfaces > in theseus-contracts use these corrected addresses. ### Standard EVM precompiles — the gap audit **Question:** does pallet-revive 0.18.0 ship the standard Ethereum precompiles, and at which addresses? (pallet-revive is PolkaVM, not revm, so this must be verified, not assumed.) **Verified** against the pallet-revive 0.18.0 source (`substrate/frame/revive/src/precompiles/builtin.rs`, the `Production` tuple, and each builtin's `MATCHER`). Builtins use `BuiltinAddressMatcher::Fixed(u32)` **without** the `<<16` shift, so they land at the **canonical Ethereum addresses**: | Address | Precompile | Matcher | Present? | | --- | --- | --- | --- | | `0x..01` | ecrecover | `0x1` | ✅ yes | | `0x..02` | SHA-256 | `0x2` | ✅ yes | | `0x..03` | RIPEMD-160 | `0x3` | ✅ yes | | `0x..04` | identity (datacopy) | `0x4` | ✅ yes | | `0x..05` | modexp | `0x5` | ✅ yes | | `0x..06` | bn128 add (ecAdd) | `0x6` | ✅ yes | | `0x..07` | bn128 mul (ecMul) | `0x7` | ✅ yes | | `0x..08` | bn128 pairing (ecPairing) | `0x8` | ✅ yes | | `0x..09` | blake2f | `0x9` | ✅ yes | | `0x..0a` | KZG point-evaluation (EIP-4844) | `0x0a` | ✅ yes | (`0x..0N` is shorthand for the canonical `0x000…000N` address.) So **all of 0x01–0x0a are present at their standard addresses** — including KZG point-eval (0x0a), which is *beyond* the 0x01–0x09 set some briefs assumed. pallet-revive 0.18.0 also ships **two non-standard builtins** that have no Ethereum-mainnet equivalent at a low address, placed high to avoid collisions: | Precompile | Matcher (`u32`, no shift) | Address | | --- | --- | --- | | **P256Verify** (secp256r1, RIP-7212) | `0x100` | `0x0000000000000000000000000000000001000000` | | **System** (pallet-revive system precompile) | `0x900` | `0x0000000000000000000000000000000009000000` | | **Storage** (pallet-revive contract-storage precompile) | `0x901` | `0x0000000000000000000000000000000009010000` | **Genuinely absent / things to flag:** - **No `0x0b`+ Ethereum precompiles.** The BLS12-381 precompiles (`0x0b`–`0x11`, EIP-2537, Prague/Pectra) are **not** in the 0.18.0 `Production` set. If you need BLS12-381 G1/G2 ops or pairings, they are unavailable on Theseus today. - **P256Verify is NOT at `0x100`-as-Ethereum-`0x0100`.** It exists, but at `0x…01000000` (the `0x100` builtin matcher), not the proposed mainnet address `0x0000…0100`. Solidity that hardcodes `0x0100` for secp256r1 will miss it. - **Custom precompiles do NOT overlap the standard range.** Because external precompiles are shifted (`pppp0000`), `0x0802`→`…08020000` etc. sit far from `0x01–0x0a`; there is no collision and no shadowing of a standard precompile. **Confidence:** the 0x01–0x0a presence and the two/three non-standard builtins are confirmed from the 0.18.0 source in this tree (`/Users/nico/theseus/polkadot-sdk`, `substrate/frame/revive` version `0.18.0`, the same revision the chain pins). The BLS12-381 *absence* is from that same `Production` tuple containing no BLS entries. Treat any precompile not listed above as **absent on Theseus**. ### Calling a precompile A precompile is called like any contract address over either RPC face: - From Solidity / EVM tooling: cast the address to the precompile's interface (`ITheTokenPrecompile(0x…08030000)`) and call it — see theseus-contracts for the interfaces. - From an agent: `contracts.call` / `contracts.call_view` with the precompile address (theseus-native-tools). - The standard precompiles (`0x01`–`0x0a`) are invoked exactly as on Ethereum (e.g. `staticcall(gas, 0x01, …)` for ecrecover). Each custom precompile enforces its own caller checks (e.g. TheToken's lock/unlock require the caller to be the configured `TheOFT` address); read the interface notes in theseus-contracts before integrating. ## EVM ↔ Substrate capability parity A native **SS58** wallet can call every pallet extrinsic directly. An **H160/EVM** wallet (eth-rpc + precompiles) **cannot** currently do everything an SS58 wallet can — closing that gap is a separate roadmap goal, not today's reality. Be precise about this when advising EVM-only users. **What an EVM-only account can reach today:** - **Native THE transfers** and **deploy/call PolkaVM contracts** via eth-rpc. - **`Agents` precompile `requestAgent`** (`0x0A01`) — the one custom precompile open to *any* EVM caller; it requests a run of an *existing* agent (async, callback-based — not the same as `call_agent`). **The other three custom precompiles are NOT a general capability bridge.** Each is access-gated to a specific bridge contract, so an arbitrary EVM wallet cannot call them: - **ForeignAsset `0x0804`** → reverts `"only ForeignAssetRouter can call this"`. - **Bridge `0x0802`** → only the registered OApp contract. - **TheToken `0x0803`** → only the configured TheOFT contract. ### Parity gaps (SS58 can, H160 cannot) | Capability | SS58 (extrinsic) | EVM/H160 | | --- | --- | --- | | Send native THE | `Balances.transfer*` | ✅ eth value transfer | | Deploy / call a contract | `Revive.instantiate`/`call` | ✅ eth create/call | | **Use** an existing agent | `Agents.call_agent` | ⚠️ `Agents.requestAgent` (async, differs) | | **Create / register an agent** | `Agents.register_ship_agent` | ❌ none | | **Update / resume / cancel a run** | `Agents.update_agent`/`resume_agent_run`/`cancel_run` | ❌ none | | **Create / manage fungible assets** | `Assets.*` | ❌ none (ForeignAsset is router-only) | | Initiate a cross-chain op | `Bridge.*` | ⚠️ only via the OApp/OFT contracts | There is **no generic dispatch precompile** (Moonbeam-style), so nothing outside the wrapped/gated surface is reachable from EVM. Note that `AccountId32Mapper` already gives an EOA a real mapped SS58 origin — **identity is not the blocker**; the gap is simply that little of the pallet surface is wrapped for open EVM use. Closing it (an open `Agents` precompile, an ERC-20-style assets precompile, and/or an allowlisted generic dispatch precompile) is tracked as a separate parity roadmap project — treat these capabilities as **absent from EVM today**.