Directory Structure
<agent-name>/
├── main.py # Agent code (run/unwind functions)
├── DESCRIPTION.md # Agent description (shown in Circuit UI)
├── circuit.toml # Agent configuration
├── pyproject.toml # Dependencies
├── AGENTS.md # AI coding assistant context
└── CLAUDE.md # Claude compatibility pointer
<agent-name>/
├── index.ts # Agent code (run/unwind functions)
├── DESCRIPTION.md # Agent description (shown in Circuit UI)
├── circuit.toml # Agent configuration
├── package.json # Dependencies
├── tsconfig.json # TypeScript compiler settings
├── AGENTS.md # AI coding assistant context
└── CLAUDE.md # Claude compatibility pointer
Agent Code
from circuit_sdk import AgentContext, SessionPortfolio
def run(agent: AgentContext) -> None:
agent.log("Hello from my agent!")
# Your agent logic here
def unwind(agent: AgentContext, portfolio: SessionPortfolio) -> None:
agent.log(f"Unwind requested for {len(portfolio.balances)} balances")
# Optional unwind logic
run Function
- Signature:
def run(agent: AgentContext) -> None:
- Called periodically based on
runtimeIntervalMinutes
- Receives
AgentContext with session data and SDK methods
- Returns
void (no return value)
- Signature:
async function run(agent: AgentContext): Promise<void>
- Called periodically based on
runtimeIntervalMinutes
- Receives
AgentContext with session data and SDK methods
- Returns
void (no return value)
unwind Function
- Signature:
def unwind(agent: AgentContext, portfolio: SessionPortfolio) -> None:
- Called when you ask the agent to unwind positions
- Optional unwind logic for the provided portfolio
- Returns
void (no return value)
- Signature:
async function unwind(agent: AgentContext, portfolio: SessionPortfolio): Promise<void>
- Called when the agent is unwound
- Optional unwind logic for the provided portfolio
- Returns
void (no return value)
The plan: DESCRIPTION.md
Every agent must include a DESCRIPTION.md file alongside circuit.toml. It is the agent’s plan — the short prose a user reads to decide whether to deploy, rendered as a card on the agent’s page in Circuit. Write all five sections below; keep each line plain, concrete, and present-tense, with real asset/protocol names and no filler.
## Summary
One or two sentences: what the agent does and why it works.
## What it is
**Long:** Gold
**Short:** Bitcoin
## How it works
- Holds a long-gold, short-BTC pair at equal notional.
- Rebalances daily to keep the legs matched.
## Strategy
- Long GOLD, short BTC at equal notional.
- Rebalance when either leg drifts past 5%.
- Exit the pair if drawdown hits the 10% kill-switch.
## Risks
- Both legs can move against you — capped by a 10% drawdown kill-switch.
- Funding/borrow costs erode returns in choppy markets.
## What it is renders as labeled chips, so its rows must use exactly the labels for your circuit.toml category — a missing or mislabeled row is silently dropped from the card. Each value is an ultra-short noun phrase (plain names, no tickers or parentheticals):
category | Labels |
|---|
spread | Long, Short |
yield | Source, Strategy, Target, Destination |
quant | Model, Trigger, Execution |
prediction | Signal, Action, Reason |
index | Model, Trigger, Execution (reuses the quant template) |
experimental | Signal, Action, Reason (reuses the prediction template) |
circuit.toml
The circuit.toml file defines your agent’s metadata, asset requirements, execution settings, and deployment configuration. It must be in the root of your agent directory.
Full Example
agentId = "8b0d9e66-3c0f-4f59-b39e-8b5c6d7b6f9a" # generated by `circuit new`; the agent's permanent identity
name = "My Agent" # max 32 characters
slug = "my-agent" # public handle; unique among your agents
tagline = "Short subtitle for cards" # max 32 characters
category = "experimental"
imageUrl = "https://cdn.circuit.org/agents/default"
walletType = "ethereum"
allowedExecutionModes = ["manual", "auto"]
runtimeIntervalMinutes = 15
schedule = "rolling"
filesToInclude = []
filesToExclude = []
[startingAsset]
network = "ethereum:1"
address = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
minimumAmount = "10000000000000000"
[settings.risk_level]
description = "How aggressively the agent trades"
type = "single_select"
default = "medium"
required = false
options = ["low", "medium", "high"]
Field Reference
| Field | Type | Required | Description |
|---|
agentId | string (UUID) | Yes | The agent’s permanent identity. Generated for you by circuit new — never edit it. Every upload updates the agent with this id, so name and slug can change freely without forking. See Agent Identity below. |
name | string | Yes | Display name shown in the Circuit UI (32 characters or less). Free to change after upload — renaming updates the same agent. |
slug | string | Yes | Public, human-readable handle (kebab-case, 64 characters or less), unique among your agents. Generated from name by circuit new; you may change it. |
tagline | string | Yes | Brief subtitle shown on agent cards (32 characters or less) |
category | string | No | Catalog category for the explore page. Allowed values: "spread", "prediction", "quant", "yield", "experimental". Defaults to "experimental". |
imageUrl | string | No | Agent icon URL. Defaults to "https://cdn.circuit.org/agents/default". |
walletType | string | No | "ethereum" or "solana". Defaults to "ethereum". |
allowedExecutionModes | string[] | No | Array of "auto" and/or "manual". First entry is the default for circuit run --hosted engine. Defaults to ["auto"]. |
runtimeIntervalMinutes | number | No | How often run() is called (in minutes; whole number, minimum 1). By default, the interval starts after the previous run completes. See schedule to align runs to clock boundaries instead. Defaults to 15. |
schedule | string | No | "rolling" (default) or "fixed". "rolling" runs again after the last run completes. "fixed" aligns runs to clock boundaries (for example :00, :15, :30, :45). Requires runtimeIntervalMinutes to divide evenly into 60. |
startingAsset | table | Yes | The token a user must hold to start a session. Required to upload. See [startingAsset] below. |
filesToInclude | string[] | No | Extra runtime files/directories to bundle (e.g. a JSON config or dataset the agent reads). Relative paths from the agent directory. Not for sharing code — use a workspace package for that. |
filesToExclude | string[] | No | Files in agent directory to exclude from upload. |
runtime | string | No | Which runtime to run on: "lambda" (default) or "cloudflare". Same agent code either way — only where it executes differs. |
settings | table | No | Configurable settings users can customize when starting a session. See [settings] below. |
Deployment Regions
| Allowed values | Default when omitted |
|---|
us-east-1, eu-central-1 | us-east-1 |
[startingAsset] Section
Defines the token a user must hold to start a session.
| Field | Type | Required | Description |
|---|
network | string | Yes | Network identifier (e.g., "ethereum:1", "solana", "hypercore:perp") |
address | string | Yes | Token contract address. For native tokens: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" (EVM) or "11111111111111111111111111111111" (Solana). For Hyperliquid perps: "USDC". |
minimumAmount | string | Yes | Minimum balance in raw units (wei, lamports, etc.). For hypercore:perp + USDC, use 8 decimal places (Circuit raw units; matches Hyperliquid USDC weiDecimals). |
Common Configurations
EVM agent with ETH on mainnet:
walletType = "ethereum"
[startingAsset]
network = "ethereum:1"
address = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
minimumAmount = "10000000000000000" # 0.01 ETH
EVM agent with USDC on Polygon:
walletType = "ethereum"
[startingAsset]
network = "ethereum:137"
address = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"
minimumAmount = "10000000" # 10 USDC (6 decimals)
Solana agent with SOL:
walletType = "solana"
[startingAsset]
network = "solana"
address = "11111111111111111111111111111111"
minimumAmount = "100000000" # 0.1 SOL (9 decimals)
Hyperliquid perps agent:
walletType = "ethereum"
[startingAsset]
network = "hypercore:perp"
address = "USDC"
minimumAmount = "100000000" # 1 USDC (8 decimals)
Agent Identity
Your agent’s identity is its agentId — a UUID that circuit new generates once and writes into circuit.toml. Every circuit upload updates the agent with that id, so all version history, sessions, and state stay attached to it. You can rename the agent (name) or change its slug freely — because identity lives in agentId, renaming updates the same agent in place instead of forking a new one.
Two rules the tooling enforces for you:
- Never edit
agentId. It’s the permanent link to your published agent; changing it points at (or creates) a different agent. To intentionally start a brand-new agent, run circuit new (which mints a fresh agentId).
slug is unique among your agents. circuit new derives it from the name and appends -2, -3, … if you already own that slug; circuit check fails (offline) if two agents in your workspace share an agentId or slug, so a copy-paste mistake can’t silently overwrite another agent on publish.
[settings] Section
Define configurable settings that users can customize when starting a session. Each setting is a TOML table under [settings.X] where X is the setting key. The display label is auto-derived from the key (snake_case to Title Case).
[settings.strategy]
description = "Which strategy the agent uses"
type = "single_select"
default = "conservative"
required = false
options = ["conservative", "aggressive"]
[settings.slippage_tolerance]
description = "Maximum slippage tolerance"
type = "percentage"
default = 0.5
required = false
[settings.buy_amount_usd]
description = "USD amount per buy order"
type = "number"
default = 50.0
required = false
[settings.max_orders_per_day]
description = "Maximum buy orders per day"
type = "integer"
default = 3
required = false
[settings.auto_compound]
description = "Automatically reinvest rewards"
type = "boolean"
default = true
required = false
[settings.treasury]
description = "Fee collection wallet"
type = "address"
default = "0x1234567890abcdef1234567890abcdef12345678"
required = false
[settings.api_key]
type = "text"
required = true
description = "Your API key for the external service"
Required settings have no default value. Users must provide a value before the agent runs. The agent will refuse to execute if any required settings are missing.
Setting fields:
| Field | Type | Required | Description |
|---|
description | string | No | Help text shown to users (max 200 characters) |
type | string | Yes | One of "text", "boolean", "single_select", "integer", "number", "percentage", "address" |
default | varies | Conditional | Default value — must match the type (see below). Required when required = false; not allowed when required = true. |
required | boolean | Yes | Whether users must provide a value. When true, no default is allowed; the agent will not run until a value is set. |
options | string[] | Conditional | Required for single_select; not allowed for other types |
Type rules:
| Type | Default value | Options |
|---|
text | String (max 1000 characters) | Not allowed |
boolean | true or false | Not allowed |
single_select | String matching one of the options | Required (max 20 options) |
integer | Whole number | Not allowed |
number | Any number (decimals allowed) | Not allowed |
percentage | Number between 0 and 100 (decimals allowed) | Not allowed |
address | Wallet address string (validated against walletType) | Not allowed |
Limits:
- Maximum 20 settings per agent
- Maximum 20 options per
single_select setting
- Options must be unique within a setting
- Setting keys must be unique across all settings
- Setting keys cannot be numeric (e.g.
0, 3.14) — use a descriptive name instead
- Default values for
single_select must match a valid option
Settings are displayed to users in the order they appear in the file.
Settings are snapshotted with each uploaded version — changing settings requires a new upload.
At runtime, resolved setting values (defaults merged with session overrides) are available on AgentContext. See Settings SDK Reference for access patterns.
Notes
circuit.toml has no version field. Each circuit upload hashes the uploaded bundle and the backend assigns the version automatically — there is nothing to bump by hand.
filesToInclude paths are relative to the agent directory, for runtime asset files (data, configs) the agent reads. To share code across agents, use a workspace package instead — see Multi-Agent Monorepo.
.circuit is reserved for CLI-managed staging during local runs. Do not store agent source files under .circuit or point filesToInclude at that directory.
allowedExecutionModes order matters — the first entry is used as the execution mode for circuit run --hosted engine.
Next Steps