Skip to content
easl

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.grid and layout.translate position objects (groups move whole).
  • object.batch applies a whole layout as one ⌘Z step, with "$0" referring to an object an earlier op created.
  • layout.check reports 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 true returns just the agent’s last answer (reported by omp, Codex, Claude Code and Gemini CLI; not opencode).
  • A handoff can carry board objects: mentions on agent.prompt reach 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.prompt to a blocked agent fails with conflict, quoting what it waits on: its approval is yours to answer.
  • agent.wait survives 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.