devin
v0.1.6Devin CLI + API as an iii worker; devin::run/start/stop/status/sessions::list follow the agent-worker family and stream AgentEvent frames onto agent::events, devin::session::* wrap the Devin cloud session lifecycle, devin::pr-review::* exposes Devin's review surface, and devin::api reaches any Devin v1/v3 endpoint.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/devin.md. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- api_key: ${DEVIN_API_KEY}
base_url: https://api.devin.ai/v1
cli_extra_args:
- --permission-mode
- dangerous
devin_executable:
events_stream: agent::events
iii_context: true
org_id: ${DEVIN_ORG_ID}
raw_events_stream: devin::events
request_timeout_secs: 120dependencies
readme
devin
Devin as an iii worker: the Devin coding agent exposed as functions and streams on the iii bus. Devin has two distinct products and this worker exposes both. The local Devin CLI is a SWE-1.6 coding agent that runs on your machine; devin::run drives it headless and, with the iii runtime context, lets it discover and operate your engine on its own. The Devin cloud agent runs in a VM, reached over the REST API: devin::session::* wrap the session lifecycle, devin::pr-review::* run reviews, and devin::api reaches any v1/v3 endpoint the typed wrappers do not cover.
This worker is deliberately thin. It does not re-implement scheduling, sub-agents, or persistence that the engine already provides. Schedule a Devin session with the cron worker, fan Devin runs out with harness::spawn, and let the engine trace and persist every call. The worker's job is to put Devin on the bus, nothing more.
Install
iii worker add devinFor the API surface (devin::session::*, devin::api), set DEVIN_API_KEY in the worker environment. Get a key from the Devin settings page.
For the CLI surface (devin::run / devin::start), install the local Devin CLI (brew install --cask devin-cli, or curl -fsSL https://cli.devin.ai/install.sh | bash) and run devin auth login. This is the local coding agent (SWE-1.6), distinct from the Devin cloud the API targets: the worker drives it in non-interactive --print mode, so the agent runs on the host, works with the local files under cwd, and returns its reply in result. Because it runs locally, an iii_context turn can reach the engine at localhost with no exposure.
Skills
Install the devin agent skill for Claude Code, Cursor, and 30+ other agents:
npx skills add iii-hq/workers --skill devinQuickstart
From zero to a Devin cloud session over the bus:
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
export DEVIN_API_KEY=... # personal token (v1) or service key (cog_..., v3)
# service-key users only: also `export DEVIN_ORG_ID=org_...` and set base_url to v3
iii worker add devin
iii # starts the engine + workerThe API has two shapes and the worker picks one from your config. A personal token uses the flat v1 API and is the default (leave org_id empty, base_url stays .../v1). A service key (cog_...) uses the v3 API scoped to an organization: set org_id and base_url to https://api.devin.ai/v3. The devin::pr-review::* functions are v3 features.
Start a cloud session and read it back:
# start an autonomous Devin session in the cloud
iii trigger devin::session::create --timeout-ms 60000 \
--json '{"prompt":"Open a PR that adds a /health endpoint to the api repo","title":"health endpoint"}'
# { "session_id": "devin-...", "url": "https://app.devin.ai/sessions/...", ... }
# poll its status, output, and messages
iii trigger devin::session::get session_id=devin-...
# send a follow-up
iii trigger devin::session::message \
--json '{"session_id":"devin-...","message":"Also add a test for it"}'
# list recent sessions
iii trigger devin::api --json '{"method":"GET","path":"sessions","query":{"limit":10}}'devin::session::create returns a real Devin cloud session over the bus (session id, url, tags):

The same session runs in the Devin app and replies:

Run the local CLI as one turn and stream it onto the bus:
iii trigger devin::run --timeout-ms 600000 \
--json '{"prompt":"summarize what this repo does","cwd":"/path/to/repo"}'
# streams raw stdout onto devin::events, AgentEvent frames onto agent::eventsReach any endpoint the typed wrappers do not cover:
# paths are relative to base_url. v1 (personal token) uses flat paths:
iii trigger devin::api --json '{"method":"GET","path":"sessions","query":{"limit":5}}'
# v3 (service key) paths are org-scoped:
iii trigger devin::api --json '{"method":"GET","path":"organizations/org_.../sessions"}'Ask the engine for any function's contract:
iii trigger devin::session::create --helpFunctions
The worker follows the same base surface as the grok, codex, claude-code, and opencode agent workers (run / start / stop / status / sessions::list / events), then adds the functions unique to Devin's cloud (session lifecycle, PR review, and a passthrough for the rest).
| Function | Surface | Purpose |
|---|---|---|
devin::run |
CLI | Run one local CLI turn, wait, return the result |
devin::start |
CLI | Start a turn and return immediately; progress on the streams |
devin::stop |
CLI | Interrupt a live CLI run |
devin::status |
CLI | A recorded run's state: live flag, status, linked Devin session id |
devin::sessions::list |
CLI | Every run this worker has recorded (each linked to its Devin session) |
devin::session::create |
Cloud | Start a Devin cloud session from a prompt |
devin::session::get |
Cloud | Fetch one session (status, messages, output) |
devin::session::message |
Cloud | Send a follow-up message to a running session |
devin::pr-review::trigger |
Cloud | Start a Devin review for a pull/merge request |
devin::pr-review::status |
Cloud | Latest Devin review for a pull/merge request |
devin::api |
Cloud | Raw authenticated call to any v1/v3 endpoint |
To list all Devin cloud sessions org-wide, use devin::api {method: GET, path: sessions} (v1) or {path: organizations/{org_id}/sessions} (v3); devin::sessions::list is scoped to the runs this worker made, matching the family convention.
devin::run / devin::start accept either a bare prompt string or a messages array ([{ role: 'user', content: [{ type: 'text', text }] }]), the same input contract as the claude-code and grok workers, so the acp worker can drive it with --brain-fn devin::run.
Because the CLI agent runs locally with the iii runtime context, a plain question makes Devin discover and operate your engine on its own, no commands spelled out:
iii trigger devin::run --json '{"prompt":"What workers are connected to this iii engine and what does each do?","cwd":"/tmp"}' | jq -r '.result'
Or ask it to map the whole engine by capability area, and it groups what it finds itself:

devin::session::create accepts prompt plus the union of the v1 and v3 create fields (title, tags, playbook_id, knowledge_ids, secret_ids, max_acu_limit; v3 devin_mode, repos, attachment_urls, resumable, bypass_approval; v1 snapshot_id, unlisted, idempotent); each is omitted from the body when not supplied, so populate the ones your token's API version accepts. devin::session::* follow the v1 flat paths by default and switch to v3 org-scoped paths when org_id is set.
PR review
devin::pr-review::trigger starts a Devin review for a pr_url; devin::pr-review::status returns the latest review for that PR. Bind a GitHub PR-opened trigger to devin::pr-review::trigger for automatic reviews. Devin's other cloud surfaces (knowledge, playbooks, secrets, repos, code scan, org admin) are reachable through devin::api when a token has access to them.
The passthrough
devin::api is the escape hatch for the full v3 surface (roughly 250 endpoints: knowledge, playbooks, secrets, repos, PR review, code scan, org and usage admin). It takes { method, path, query?, body? }, adds the bearer token and organization header, and returns the parsed response. Reach for it whenever a capability is not one of the typed wrappers above; graduate a wrapper only when a call proves common.
Streams and observability
devin::run mirrors every stdout line from the CLI verbatim onto devin::events (group_id = session_id) and emits a terminal AgentEvent frame onto agent::events, so the iii console renders a Devin CLI turn like any other agent worker. The cloud functions return their JSON directly and do not stream; poll devin::session::get for progress, or bind a cron trigger to poll on a schedule instead of looping.
Every devin::* call is a traced invocation on the engine with no extra instrumentation: the input payload, output, duration, and ok/error land in the console's trace explorer, including the iii trigger calls a devin::run agent makes on its own.

Configuration
Managed by the configuration worker; config.yaml is the seed installed on first registration and the live value hot-reloads.
api_key: "${DEVIN_API_KEY}" # env-expanded on load; empty disables the API surface
org_id: "${DEVIN_ORG_ID}" # empty = v1 personal mode; set = v3 org-scoped mode
base_url: https://api.devin.ai/v1 # set to .../v3 alongside org_id for a service key
request_timeout_secs: 120
devin_executable: "" # path to the devin CLI; empty = PATH
cli_extra_args: ["--permission-mode", "dangerous"] # before `--print --`; dangerous lets the agent run iii trigger
events_stream: agent::events # AgentEvent frames
raw_events_stream: devin::events # verbatim CLI stdout
iii_context: true # prepend iii runtime context to a CLI promptapi_key and org_id are referenced as ${DEVIN_API_KEY} and ${DEVIN_ORG_ID} and expanded from the environment on load (an unset var becomes empty), so neither secret lives in the repo. An empty api_key disables the API surface while the CLI surface still works if the local devin binary is authenticated. org_id selects the API shape: empty uses the flat v1 session paths (personal tokens), set uses the v3 org-scoped paths (service keys) and becomes the required path segment for pr-review.
iii_context defaults on: a devin::run turn is prepended with the iii runtime context so the local agent discovers and calls engine functions through the iii CLI (turn it off per turn with iii_context: false). cli_extra_args defaults to --permission-mode dangerous, which is the only devin CLI mode that auto-approves command execution, so a headless run can actually run iii trigger against the engine; the local agent then auto-approves all tools, so drop to accept-edits or auto to restrict it.
Dependent workers
configuration(required): holds the API key, base URL, and stream names; hot-reloads changes.cron(optional): scheduledevin::session::createor polldevin::session::getwithout a polling loop.harness(optional): fan multiple Devin runs out as sub-agents withharness::spawn.
Permissions
devin::run, devin::start, devin::session::create, devin::session::message, and devin::api drive or mutate a Devin agent and spend ACUs, so they stay at the needs_approval default; an agent invoking them without human approval is a privilege escalation. The read-only introspection functions (devin::status, devin::sessions::list, devin::session::get, devin::pr-review::status) and devin::stop are allow-listed in iii-permissions.yaml.
How it maps
| Devin | iii |
|---|---|
one local devin --print -- turn (SWE-1.6 agent) |
devin::run invocation |
| every CLI stdout line, verbatim | devin::events stream frame |
| a Devin cloud session | devin::session::create / ::get / ::message |
| a Devin PR review | devin::pr-review::trigger / ::status |
| any other v1/v3 endpoint | devin::api passthrough |
| scheduling a run | cron worker trigger, not a worker feature |
| fanning runs out | harness::spawn, not a worker feature |
api reference (json)
{
"functions": [
{
"description": "Raw authenticated call to any Devin v3 endpoint: {method, path, query?, body?}. Use for anything the typed devin::session::* wrappers do not cover.",
"metadata": {},
"name": "devin::api",
"request_schema": {
"description": "A raw pass-through call to any Devin v3 endpoint. Use this for anything the typed wrappers do not cover. Paths are relative to the configured base URL (e.g. `organizations/{org_id}/sessions`).",
"properties": {
"body": {
"description": "Optional JSON request body."
},
"method": {
"description": "HTTP method: GET, POST, PUT, PATCH, or DELETE.",
"type": "string"
},
"path": {
"description": "Path relative to the configured base URL, e.g. `organizations/{org_id}/sessions`.",
"type": "string"
},
"query": {
"description": "Optional query parameters as a flat object."
}
},
"required": [
"method",
"path"
],
"title": "ApiRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "Internal: reload devin configuration when it changes.",
"metadata": {},
"name": "devin::on-config-change",
"request_schema": {
"properties": {},
"type": "object"
},
"response_schema": {
"properties": {
"ok": {
"type": "boolean"
}
},
"type": "object"
}
},
{
"description": "Get the latest Devin review status for a pull/merge request.",
"metadata": {},
"name": "devin::pr-review::status",
"request_schema": {
"description": "Look up the latest Devin PR review for a pull/merge request.",
"properties": {
"commit_sha": {
"description": "Commit SHA (full or short); defaults to the PR head when omitted.",
"type": [
"string",
"null"
]
},
"pr_url": {
"description": "Full URL of the pull/merge request to look up.",
"type": "string"
}
},
"required": [
"pr_url"
],
"title": "PrReviewStatusRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "Trigger a Devin review for a pull/merge request by its URL.",
"metadata": {},
"name": "devin::pr-review::trigger",
"request_schema": {
"description": "Trigger a Devin PR review for a pull/merge request.",
"properties": {
"pr_url": {
"description": "Full URL of the pull/merge request to review.",
"type": "string"
}
},
"required": [
"pr_url"
],
"title": "PrReviewTriggerRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "Run one Devin CLI turn and wait for the result. Accepts `prompt` or a `messages` array; streams raw CLI stdout onto devin::events, terminal AgentEvent frames onto agent::events, and returns {session_id, devin_session_id, url, result, stop_reason, is_error}.",
"metadata": {},
"name": "devin::run",
"request_schema": {
"definitions": {
"Message": {
"properties": {
"content": {
"description": "Either a plain string or an array of content blocks."
},
"role": {
"type": "string"
}
},
"required": [
"content",
"role"
],
"type": "object"
}
},
"properties": {
"cwd": {
"default": null,
"description": "Working directory the CLI runs in. Empty = the worker's process directory.",
"type": [
"string",
"null"
]
},
"iii_context": {
"default": null,
"description": "Prepend the iii runtime discovery prompt as leading instructions (default from config).",
"type": [
"boolean",
"null"
]
},
"messages": {
"description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
"items": {
"$ref": "#/definitions/Message"
},
"type": [
"array",
"null"
]
},
"prompt": {
"default": null,
"description": "The prompt handed to the `devin` CLI for this turn.",
"type": [
"string",
"null"
]
},
"session_id": {
"default": null,
"description": "iii session id; reuse to keep the same local record. Omit to generate one.",
"type": [
"string",
"null"
]
}
},
"title": "RunRequest",
"type": "object"
},
"response_schema": {
"properties": {
"busy": {
"type": "boolean"
},
"devin_session_id": {
"type": [
"string",
"null"
]
},
"is_error": {
"type": "boolean"
},
"num_turns": {
"type": "integer"
},
"reason": {
"type": "string"
},
"result": {
"type": "string"
},
"session_id": {
"type": "string"
},
"stop_reason": {
"type": "string"
},
"url": {
"type": [
"string",
"null"
]
}
},
"type": "object"
}
},
{
"description": "Create a Devin cloud session from a prompt. Optional fields (title, tags, playbook_id, snapshot_id, idempotent, max_acu_limit, secret_ids, knowledge_ids, unlisted) map to POST /sessions.",
"metadata": {},
"name": "devin::session::create",
"request_schema": {
"definitions": {
"Message": {
"properties": {
"content": {
"description": "Either a plain string or an array of content blocks."
},
"role": {
"type": "string"
}
},
"required": [
"content",
"role"
],
"type": "object"
}
},
"description": "Create a Devin session. `prompt` (or a user `messages` entry) is required. The remaining fields are the union of the v1 (`POST /v1/sessions`) and v3 (`POST /v3/organizations/{org_id}/sessions`) bodies; each is omitted when absent, so populate the ones your token's API version accepts.",
"properties": {
"attachment_urls": {
"default": null,
"description": "(v3) File attachment URLs.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"bypass_approval": {
"default": null,
"description": "(v3) Skip approval workflows for this session.",
"type": [
"boolean",
"null"
]
},
"devin_mode": {
"default": null,
"description": "(v3) Agent mode: normal, fast, lite, ultra, or fusion.",
"type": [
"string",
"null"
]
},
"idempotent": {
"default": null,
"description": "(v1) Reuse an existing matching session instead of creating a duplicate.",
"type": [
"boolean",
"null"
]
},
"knowledge_ids": {
"default": null,
"description": "Knowledge ids to attach to the session.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"max_acu_limit": {
"default": null,
"description": "Cap the session's ACU consumption.",
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"messages": {
"description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
"items": {
"$ref": "#/definitions/Message"
},
"type": [
"array",
"null"
]
},
"playbook_id": {
"default": null,
"description": "Playbook id to run.",
"type": [
"string",
"null"
]
},
"prompt": {
"default": null,
"description": "The task prompt for the new Devin session.",
"type": [
"string",
"null"
]
},
"repos": {
"default": null,
"description": "(v3) Repository identifiers to attach to the session.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"resumable": {
"default": null,
"description": "(v3) Preserve VM state so the session can be resumed (default true).",
"type": [
"boolean",
"null"
]
},
"secret_ids": {
"default": null,
"description": "Secret ids to expose to the session.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"snapshot_id": {
"default": null,
"description": "(v1) Machine snapshot id to start the session from.",
"type": [
"string",
"null"
]
},
"tags": {
"default": null,
"description": "Tags to apply to the session.",
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"title": {
"default": null,
"description": "Human-readable session title.",
"type": [
"string",
"null"
]
},
"unlisted": {
"default": null,
"description": "(v1) Create the session as unlisted.",
"type": [
"boolean",
"null"
]
}
},
"title": "SessionCreateRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "Fetch one Devin cloud session (status, messages, output) by session id.",
"metadata": {},
"name": "devin::session::get",
"request_schema": {
"properties": {
"session_id": {
"description": "iii session id (devin::run) or Devin cloud session id (devin::session::*).",
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionIdRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "Send a follow-up message to a running Devin cloud session.",
"metadata": {},
"name": "devin::session::message",
"request_schema": {
"description": "Send a follow-up message to a running Devin session.",
"properties": {
"message": {
"description": "The message to send.",
"type": "string"
},
"session_id": {
"description": "Devin cloud session id (returned by devin::session::create).",
"type": "string"
}
},
"required": [
"message",
"session_id"
],
"title": "SessionMessageRequest",
"type": "object"
},
"response_schema": {
"additionalProperties": true,
"type": "object"
}
},
{
"description": "List every devin::run / devin::start session this worker has recorded, each linked to its Devin session id. For all Devin cloud sessions org-wide, use devin::api {method: GET, path: sessions}.",
"metadata": {},
"name": "devin::sessions::list",
"request_schema": {
"properties": {},
"type": "object"
},
"response_schema": {
"properties": {
"sessions": {
"items": {
"type": "object"
},
"type": "array"
}
},
"type": "object"
}
},
{
"description": "Start a Devin CLI turn and return immediately; watch devin::events / agent::events (group_id = session_id) for progress and turn_end.",
"metadata": {},
"name": "devin::start",
"request_schema": {
"definitions": {
"Message": {
"properties": {
"content": {
"description": "Either a plain string or an array of content blocks."
},
"role": {
"type": "string"
}
},
"required": [
"content",
"role"
],
"type": "object"
}
},
"properties": {
"cwd": {
"default": null,
"description": "Working directory the CLI runs in. Empty = the worker's process directory.",
"type": [
"string",
"null"
]
},
"iii_context": {
"default": null,
"description": "Prepend the iii runtime discovery prompt as leading instructions (default from config).",
"type": [
"boolean",
"null"
]
},
"messages": {
"description": "Alternative to prompt: role/content messages; the last user entry becomes the prompt.",
"items": {
"$ref": "#/definitions/Message"
},
"type": [
"array",
"null"
]
},
"prompt": {
"default": null,
"description": "The prompt handed to the `devin` CLI for this turn.",
"type": [
"string",
"null"
]
},
"session_id": {
"default": null,
"description": "iii session id; reuse to keep the same local record. Omit to generate one.",
"type": [
"string",
"null"
]
}
},
"title": "RunRequest",
"type": "object"
},
"response_schema": {
"properties": {
"session_id": {
"type": "string"
},
"started": {
"type": "boolean"
}
},
"type": "object"
}
},
{
"description": "Point-in-time status of a local devin::run session.",
"metadata": {},
"name": "devin::status",
"request_schema": {
"properties": {
"session_id": {
"description": "iii session id (devin::run) or Devin cloud session id (devin::session::*).",
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionIdRequest",
"type": "object"
},
"response_schema": {
"properties": {
"live": {
"type": "boolean"
},
"record": {
"type": [
"object",
"null"
]
},
"session_id": {
"type": "string"
}
},
"type": "object"
}
},
{
"description": "Interrupt a live devin::run CLI turn for a session.",
"metadata": {},
"name": "devin::stop",
"request_schema": {
"properties": {
"session_id": {
"description": "iii session id (devin::run) or Devin cloud session id (devin::session::*).",
"type": "string"
}
},
"required": [
"session_id"
],
"title": "SessionIdRequest",
"type": "object"
},
"response_schema": {
"properties": {
"reason": {
"type": [
"string",
"null"
]
},
"session_id": {
"type": "string"
},
"stopped": {
"type": "boolean"
}
},
"type": "object"
}
}
],
"triggers": []
}