Docs For agents
Agent API
The local socket, schema, Python SDK, TypeScript client and easl CLI that agents use to work on the board, talk to each other and share helpers.
All docs pages
Agents don’t just run next to the board; they work on it. Through the easl API an agent creates and lays out tiles in atomic batches, measures and fits content, checks a layout, renders any region offscreen without moving your view, points you at things, and reads the board’s activity history.
Pieces
| Piece | What it is |
|---|---|
| Socket | A local Unix socket, ~/Library/Application Support/Easl/easl.sock (mode 0600) |
| Schema | schema/easl-api.json: the method catalog, the single source of truth |
| Python SDK | easl_sdk: recommended for any agent with a persistent Python REPL |
| TypeScript client | CanvasClient, used by the omp extension and usable from JS |
easl CLI |
For agents without a REPL; on PATH in every easl terminal tile |
| Skill | skills/easl/SKILL.md, which teaches agents to work on the board |
Every terminal tile’s environment has EASL_ENV=1, EASL_SOCKET, EASL_TILE_ID (this tile), EASL_BOARD_ID and EASL_BOARD_ROOT (the directory the board belongs to: the repository’s main checkout, whichever worktree the tile is in). The clients read them, so objects an agent creates are credited to its terminal and placed next to it.
The easl CLI
Methods are namespace.method; params are --key value (values parse as JSON when they can), a bare --flag (true), or --json '{…}' (--json @params.json, or @- for stdin).
easl methods # every method with its description
easl methods view.render # its params (types, defaults, required) and result
easl methods CodeProps # a type's props (any *Props: NoteProps, HtmlProps, …)
easl object.create --type note --json '{"props":{"markdown":"# Plan"}}'
easl get obj_… --as graph # object.get shorthand
easl render obj_… # view.render shorthand; prints the PNG path
Results print as JSON on stdout; errors print code: message on stderr and exit 1.
The Python SDK
from easl_sdk import canvas
board = canvas.board.get() # manifest of every object
note = canvas.object.create(type="note", props={"markdown": "# Plan"})["object"]
canvas.agent.wait(target="reviewer", timeout_ms=600000) # keywords are snake_case (CLI: --timeoutMs)
Keywords are snake_case (timeout_ms, session_id); a reserved word takes a trailing underscore (as_="graph"). Errors raise CanvasError with a .code. help(canvas.<ns>.<method>) shows a method’s signature.
If a kernel doesn’t inherit the tile’s environment, connect explicitly:
from easl_sdk import connect
canvas = connect(socket="…/easl.sock", tile="obj_…", board="brd_…")
The TypeScript client
const client = new CanvasClient({ socketPath, tile, board }); // all optional
await client.api.object.get({ id: "obj_…" });
Methods live under client.api.<ns>.<method>({…}); the client comes from clients/ts/src/index.ts.
Results and errors
Results are objects, never bare values: object.create, object.update and object.get return {object}, so the new id is result["object"]["id"]. A prop key the type doesn’t know (a typo like colour) comes back in warnings.
| Error code | Means |
|---|---|
not_found |
No such object, agent or board (or a code tile’s file or commit) |
conflict |
A stale rev: re-read, re-apply, retry |
invalid_params |
A param the method doesn’t take, or a required one missing; the message lists every param it takes |
unavailable |
The app isn’t running, or a terminal has no running session |
unsupported |
The method can’t do that for this object |
timeout |
agent.wait ran out of time |
If the app restarts, the next call reconnects by itself (waiting up to 15 seconds).
Show work on the board
| To show | Create |
|---|---|
| Real code | code: {"path": "src/store.ts", "range": {"start": 41, "end": 60}, "caption": "restore replays the log"} |
| Notes, plans, findings | note: {"markdown": "…"} |
| A chart or figure | image: {"path": "out/fig.png", "caption": "…"} |
| An explainer or comparison | html: {"html": "…", "title": "…"} |
| What you changed | changes: {} with "size": "fit" |
| A web page | browser: {"url": "http://localhost:3000"} |
| Structure | shape, arrow, group |
easl object.create --type changes --json '{"props":{},"size":"fit"}'
Leave out frame and the object lands in the free spot nearest the agent’s terminal, inside your view when there’s room. For deliberate layouts:
size: "fit"sizes a tile to its content.layout.place,layout.stack,layout.gridandlayout.translateposition objects (groups move whole).object.batchapplies a whole layout as one ⌘Z step, with"$0"referring to an object an earlier op created.layout.checkreports overlaps, arrows through tiles, labels on tiles, content that doesn’t fit, and cut-off captions. An empty report means a clean picture.
canvas.object.batch(ops=[
{"method": "object.create", "params": {"type": "code", "props": {"path": "src/a.ts", "range": {"start": 10, "end": 30}}, "size": "fit", "frame": {"x": 0, "y": 0}}},
{"method": "object.create", "params": {"type": "note", "props": {"markdown": "Why this matters"}, "size": "fit", "frame": {"x": 0, "y": 0, "w": 320}}},
{"method": "layout.place", "params": {"id": "$1", "near": "$0", "side": "below", "gap": 14}},
{"method": "object.create", "params": {"type": "group", "props": {"members": ["$0", "$1"], "title": "Request path", "color": "blue"}}},
])
For a walkthrough, join the stops with "relation": "next_step" arrows; ⌥⌘→ and ⌥⌘← then step along them.
See the board
| Want | Call |
|---|---|
| Look at objects or a region, wherever the user is | view.render |
| What the user is looking at right now, as pixels | view.snapshot |
| Where the user is looking, without pixels | view.get |
| What happened since you last looked | board.history |
easl render obj_… # one object (the canvas region under it)
easl render obj_a,obj_b --scale 2 # the region covering several
easl render 0,1200,2400,1600 --exclude '["terminal"]' # a canvas rect x,y,w,h
easl render obj_… --full # a note/HTML tile's whole content
view.render never moves your view. Without --out it writes a new PNG under $TMPDIR/easl-renders/ and returns its path.
Point the user at something
Agents never move your view unless you ask. They raise an attention marker instead:
easl view.attention --id obj_… --message "The race is here" # → {"id": "obj_…", "active": true}
easl view.attention --id obj_… --clear # take it back
Agent to agent
Agents in other terminal tiles, on any open board, are reachable by tile id or tile name:
easl agent.list # every terminal: tile, kind, name, lifecycle, board, root, program, title
easl agent.prompt --target reviewer --text "Review the diff in src/store.ts" # → waitable, submittedAt
easl agent.wait --target reviewer --timeoutMs 600000 # until idle/done/blocked
easl agent.read --target reviewer --since prompt # only what came after your last agent.prompt
agent.read --final truereturns just the agent’s last answer (reported by omp, Codex, Claude Code and Gemini CLI; not opencode).- A handoff can carry board objects:
mentionsonagent.promptreach the receiver as hidden context naming the sender, like your Hyper-click mentions.
canvas.agent.prompt(target="fees", text="review the findings note against the code, read-only",
mentions=[{"object": note_id}, {"object": code_id, "lines": {"start": 41, "end": 48}}])
canvas.agent.wait(target="fees", timeout_ms=900_000) # done, idle, or blocked
reply = canvas.agent.read(target="fees", final=True)["text"]
agent.promptto a blocked agent fails withconflict, quoting what it waits on: its approval is yours to answer.agent.waitsurvives an app restart: the clients ask again once the app is back.
Compositions
Reusable helpers ship with both SDKs, plus your own in ~/.easl/compositions/, which shadow built-in ones of the same name.
canvas.compositions.available() # name -> summary
canvas.compositions.grid.arrange([id1, id2, id3]) # grid beside your terminal, clear of other tiles
canvas.compositions.locations.open(["src/a.ts:12-40", "src/b.ts#L7"])
A composition is a plain module: write ~/.easl/compositions/<name>.py (and .ts for the TypeScript client), then canvas.compositions.reload().
Boards
easl board.open --root <absolute dir> # open a directory's board as a tab, behind the current one
easl board.list # every stored board, archived ones too
easl board.export # write <root>/.easl/board.json for committing
The skill
easl ships a skill, skills/easl/SKILL.md, that teaches agents to work on the board: which client to use, where objects land, how to read mentions, how to show changes, and how to reach other agents. The integrations offer it only inside easl (when EASL_ENV=1): omp and Codex are told where it is, and Claude Code loads it as the plugin’s easl:easl skill. Nothing is added to your global agent config.