Skip to main content

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 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)

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)

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):
categoryLabels
spreadLong, Short
yieldSource, Strategy, Target, Destination
quantModel, Trigger, Execution
predictionSignal, Action, Reason
indexModel, Trigger, Execution (reuses the quant template)
experimentalSignal, 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

FieldTypeRequiredDescription
agentIdstring (UUID)YesThe 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.
namestringYesDisplay name shown in the Circuit UI (32 characters or less). Free to change after upload — renaming updates the same agent.
slugstringYesPublic, human-readable handle (kebab-case, 64 characters or less), unique among your agents. Generated from name by circuit new; you may change it.
taglinestringYesBrief subtitle shown on agent cards (32 characters or less)
categorystringNoCatalog category for the explore page. Allowed values: "spread", "prediction", "quant", "yield", "experimental". Defaults to "experimental".
imageUrlstringNoAgent icon URL. Defaults to "https://cdn.circuit.org/agents/default".
walletTypestringNo"ethereum" or "solana". Defaults to "ethereum".
allowedExecutionModesstring[]NoArray of "auto" and/or "manual". First entry is the default for circuit run --hosted engine. Defaults to ["auto"].
runtimeIntervalMinutesnumberNoHow 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.
schedulestringNo"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.
startingAssettableYesThe token a user must hold to start a session. Required to upload. See [startingAsset] below.
filesToIncludestring[]NoExtra 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.
filesToExcludestring[]NoFiles in agent directory to exclude from upload.
runtimestringNoWhich runtime to run on: "lambda" (default) or "cloudflare". Same agent code either way — only where it executes differs.
settingstableNoConfigurable settings users can customize when starting a session. See [settings] below.

Deployment Regions

Allowed valuesDefault when omitted
us-east-1, eu-central-1us-east-1

[startingAsset] Section

Defines the token a user must hold to start a session.
FieldTypeRequiredDescription
networkstringYesNetwork identifier (e.g., "ethereum:1", "solana", "hypercore:perp")
addressstringYesToken contract address. For native tokens: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" (EVM) or "11111111111111111111111111111111" (Solana). For Hyperliquid perps: "USDC".
minimumAmountstringYesMinimum 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:
FieldTypeRequiredDescription
descriptionstringNoHelp text shown to users (max 200 characters)
typestringYesOne of "text", "boolean", "single_select", "integer", "number", "percentage", "address"
defaultvariesConditionalDefault value — must match the type (see below). Required when required = false; not allowed when required = true.
requiredbooleanYesWhether users must provide a value. When true, no default is allowed; the agent will not run until a value is set.
optionsstring[]ConditionalRequired for single_select; not allowed for other types
Type rules:
TypeDefault valueOptions
textString (max 1000 characters)Not allowed
booleantrue or falseNot allowed
single_selectString matching one of the optionsRequired (max 20 options)
integerWhole numberNot allowed
numberAny number (decimals allowed)Not allowed
percentageNumber between 0 and 100 (decimals allowed)Not allowed
addressWallet 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