Developers
Every call, event and error.
The plugin API 1.0, generated from its schema: what a plugin sends, what the host sends, the events, the errors and the methods the app uses to manage plugins, each with its params, its result and a worked example.
On this page
Each entry below is generated from the plugin API 1.0 schema. A member marked required must be there. A plugin must ignore members, event kinds, error codes and values it does not know, because a later minor version adds them; it never removes one.
The four message shapes
A plugin and the host talk over the plugin's standard input and output: one JSON object per line, at most 65,536 bytes, in UTF-8. Each line is one of these four shapes. The daemon methods at the end of this page use the daemon's own socket instead.
Request
Asks the other side to do something and expects exactly one response or failure with the same id. params is always present and always an object.
idrequiredstring
- matches
^[A-Za-z0-9_.:-]{1,64}$
Chosen by the sender of a request and repeated in its response. Each side numbers its own requests; the two id spaces are separate.
- matches
methodrequiredone of
"events.subscribe","events.unsubscribe","sessions.list","lanes.list","seats.list","storage.get","storage.set","storage.delete","storage.list","tasks.create","initialize","ping","command.run","shutdown"paramsrequiredobject
No other members are allowed.
- When
methodis"events.subscribe",paramsisevents.subscribe params. - When
methodis"events.unsubscribe",paramsisevents.unsubscribe params. - When
methodis"sessions.list",paramsissessions.list params. - When
methodis"lanes.list",paramsislanes.list params. - When
methodis"seats.list",paramsisseats.list params. - When
methodis"storage.get",paramsisstorage.get params. - When
methodis"storage.set",paramsisstorage.set params. - When
methodis"storage.delete",paramsisstorage.delete params. - When
methodis"storage.list",paramsisstorage.list params. - When
methodis"tasks.create",paramsistasks.create params. - When
methodis"initialize",paramsisinitialize params. - When
methodis"ping",paramsisping params. - When
methodis"command.run",paramsiscommand.run params. - When
methodis"shutdown",paramsisshutdown params.
{
"id": "1",
"method": "events.subscribe",
"params": {
"kinds": [
"turn.end",
"lane.verdict"
]
}
}{
"id": "h-1",
"method": "shutdown",
"params": {
"reason": "disabled"
}
}Response
The answer to a request that succeeded. result is always an object; its shape is the result of the call the id belongs to.
idrequiredstring
- matches
^[A-Za-z0-9_.:-]{1,64}$
Chosen by the sender of a request and repeated in its response. Each side numbers its own requests; the two id spaces are separate.
- matches
resultrequiredobject
No other members are allowed.
{
"id": "1",
"result": {
"kinds": [
"turn.end",
"lane.verdict"
]
}
}Failure
The answer to a request that was refused or failed.
idrequiredstring
- matches
^[A-Za-z0-9_.:-]{1,64}$
Chosen by the sender of a request and repeated in its response. Each side numbers its own requests; the two id spaces are separate.
- matches
errorrequiredobject
- no other members
The error member of a failure, in either direction. code is lowercase words joined by hyphens; message is text for a person and is never parsed. A plugin's message may be any text that fits the line, also longer than 500 characters or with line breaks or other control characters: the failure is valid, and the host cleans the message and cuts it to 500 characters before a client sees it. What the host itself sends is also valid against hostError, whose message is one line of at most 500 characters.
error.coderequiredstring
- at most 64 characters
- matches
^[a-z]+(?:-[a-z]+)*$
error.messagerequiredstring
error.dataoptionalobject
No other members are allowed.
{
"id": "9",
"error": {
"code": "method-not-found",
"message": "Unknown method: storage.clear"
}
}Notification
Tells the other side something and expects no answer. It has no id. API 1.0 has two, both from the host.
methodrequiredone of
"event","events.dropped"paramsrequiredobject
No other members are allowed.
- When
methodis"event",paramsisevent. - When
methodis"events.dropped",paramsisevents.dropped.
{
"method": "event",
"params": {
"kind": "turn.end",
"ts": "2026-11-14T09:31:07.412Z",
"seq": 58,
"session": "s-4f9a1c2b7d3e",
"data": {
"reason": "completed"
}
}
}Calls a plugin makes to the host
events.subscribe
- A plugin sends it to the host
- Permission
events.read
Starts or changes the delivery of events. Without kinds the plugin gets every kind of API 1.0. A second call replaces the first. Delivery starts with the first event the daemon records after the response; there is no replay.
Params
kindsoptionalarray; each item is one of
"session.start","session.end","turn.start","turn.end","approval.request","approval.resolved","lane.start","lane.stage","lane.verdict"- 1 to 9 items
- no item twice
No other members are allowed.
Result
kindsrequiredarray; each item is one of
"session.start","session.end","turn.start","turn.end","approval.request","approval.resolved","lane.start","lane.stage","lane.verdict"- 1 to 9 items
- no item twice
The kinds now delivered, in the order of the kind list.
No other members are allowed.
Errors
Only the errors every call can get.
Example 1
{
"kinds": [
"turn.end",
"lane.verdict"
]
}{
"kinds": [
"turn.end",
"lane.verdict"
]
}Example 2
{}{
"kinds": [
"session.start",
"session.end",
"turn.start",
"turn.end",
"approval.request",
"approval.resolved",
"lane.start",
"lane.stage",
"lane.verdict"
]
}events.unsubscribe
- A plugin sends it to the host
- Permission
events.read
Stops the delivery of events. Events already written to the plugin's input still arrive.
Params
None: params is an empty object.
Result
kindsrequiredarray
- at most 0 items
No other members are allowed.
Errors
Only the errors every call can get.
Example
{}{
"kinds": []
}sessions.list
- A plugin sends it to the host
- Permission
lists.read
One page of the daemon's sessions in creation order, this machine's first, then the sessions it follows on other nodes. A page holds at most limit sessions and never more than fit one line; nextCursor is null on the last page.
Params
cursoroptionalstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The nextCursor of the page before.
- matches
limitoptionalinteger
- from 1 to 500
- default
100
No other members are allowed.
Result
sessionsrequiredarray; each item is object
- at most 500 items
sessions[].sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
sessions[].harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
sessions[].providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
sessions[].transportrequiredone of
"acp","claude-pty-hooks","stream-json","muse-msp","omp-rpc"How the daemon drives the session's agent.
sessions[].staterequiredone of
"starting","idle","running","awaiting-approval","ended"sessions[].turnrequiredinteger
- at least 0
How many turns the session has started.
sessions[].turnActiverequiredboolean
sessions[].endReasonrequiredone of
"completed","failed","interrupted","usage-limited"or nullHow the last turn ended; null while a turn runs and before the first one has ended.
sessions[].pendingApprovalsrequiredinteger
- at least 0
sessions[].parentrequiredstring or null
- matches
^[a-zA-Z0-9_-]{1,128}$
The session this one was handed off from, or null.
- matches
sessions[].noderequiredstring
- matches
^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$
The machine the session runs on, as the daemon names it, or unknown.
- matches
sessions[].worktreerequiredstring
- at most 4,096 characters
- matches
^(?:/|[A-Za-z]:[\\/]|\\\\)[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
The folder the session works in.
sessions[].createdAtrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
nextCursorrequiredstring or null
- matches
^[a-zA-Z0-9_-]{1,128}$
- matches
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example
{
"limit": 2
}{
"sessions": [
{
"session": "s-4f9a1c2b7d3e",
"harness": "claude",
"provider": "anthropic",
"transport": "claude-pty-hooks",
"state": "running",
"turn": 1,
"turnActive": true,
"endReason": null,
"pendingApprovals": 0,
"parent": null,
"node": "desk",
"worktree": "/srv/work/shop-wt/fix-login",
"createdAt": "2026-11-14T09:12:44.031Z"
},
{
"session": "s-7a1b2c3d4e5f",
"harness": "codex",
"provider": "openai",
"transport": "stream-json",
"state": "ended",
"turn": 1,
"turnActive": false,
"endReason": "completed",
"pendingApprovals": 0,
"parent": null,
"node": "desk",
"worktree": "/srv/work/shop-wt/fix-login",
"createdAt": "2026-11-14T09:20:02.118Z"
}
],
"nextCursor": "s-7a1b2c3d4e5f"
}lanes.list
- A plugin sends it to the host
- Permission
lists.read
One page of the lanes the daemon dispatched, newest first, as many as the daemon's verify.list put in its page. A lane the daemon archived after its merge is not listed. The host answers from a copy it reads from the daemon at most once in 5 seconds.
Params
cursoroptionalstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z/t-[0-9a-f]{12}$
The nextCursor of the page before.
- matches
No other members are allowed.
Result
lanesrequiredarray; each item is object
lanes[].lanerequiredstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
lanes[].reporequiredstring
- at most 4,096 characters
- matches
^(?:/|[A-Za-z]:[\\/]|\\\\)[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
An absolute path of this machine: /... on Linux and macOS, a drive or a share on Windows. No control or invisible formatting character (Unicode categories Cc, Cf, Cs, Zl, Zp).
lanes[].baserequiredstring
- 1 to 255 characters
- matches
^[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp} ~^:?*\[\\]+$
A git branch name, without control or invisible formatting characters.
lanes[].branchrequiredstring
- 1 to 255 characters
- matches
^[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp} ~^:?*\[\\]+$
A git branch name, without control or invisible formatting characters.
lanes[].worktreerequiredstring
- at most 4,096 characters
- matches
^(?:/|[A-Za-z]:[\\/]|\\\\)[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
An absolute path of this machine: /... on Linux and macOS, a drive or a share on Windows. No control or invisible formatting character (Unicode categories Cc, Cf, Cs, Zl, Zp).
lanes[].staterequiredone of
"building","under-test","sent-back","verified","merged"The lane's state as verify.list gives it.
lanes[].stagerequiredone of
"building","idle","checking","waiting-verifier","verifying","verify-failed","second-opinion","pass","rework","sent-back","cap","merging","merge-failed","merged","closed"Where a lane is in the daemon's lane loop.
lanes[].builderrequiredobject
- no other members
The seat that builds the lane now (after a rework or a failover, the latest), and its session.
lanes[].builder.sessionrequiredstring or null
- matches
^[a-zA-Z0-9_-]{1,128}$
- matches
lanes[].builder.harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
lanes[].builder.providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
lanes[].builder.familyrequiredone of
"anthropic","openai","google","xai","moonshot","deepseek","local","auto"A provider family of the registry.
lanes[].builder.modelrequiredstring or null
- matches
^[A-Za-z0-9][A-Za-z0-9_.:/\[\]-]{0,127}$
A model name from a seat's approved arguments, or null when the seat names none or the name is not a plain model name.
- matches
lanes[].verifierrequiredobject
- no other members
The seat a lane was built or is verified on.
lanes[].verifier.harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
lanes[].verifier.providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
lanes[].verifier.familyrequiredone of
"anthropic","openai","google","xai","moonshot","deepseek","local","auto"A provider family of the registry.
lanes[].verifier.modelrequiredstring or null
- matches
^[A-Za-z0-9][A-Za-z0-9_.:/\[\]-]{0,127}$
A model name from a seat's approved arguments, or null when the seat names none or the name is not a plain model name.
- matches
lanes[].verdictrequirednull or object
The latest verdict, or null before the first.
lanes[].verdict.verdictrequiredone of
"pass","rework"lanes[].verdict.commitrequiredstring
- matches
^(?:[0-9a-f]{40}|[0-9a-f]{64})$
A full git commit id.
- matches
lanes[].headrequiredstring or null
- matches
^(?:[0-9a-f]{40}|[0-9a-f]{64})$
The commit the lane's branch points at now, or null when the branch or the repository is gone.
- matches
lanes[].stalerequiredboolean
True when the verdict is on another commit than head.
lanes[].createdAtrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
nextCursorrequiredstring or null
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z/t-[0-9a-f]{12}$
- matches
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example
{}{
"lanes": [
{
"lane": "t-0c1d2e3f4a5b",
"repo": "/srv/work/shop",
"base": "main",
"branch": "omni/fix-login",
"worktree": "/srv/work/shop-wt/fix-login",
"state": "verified",
"stage": "pass",
"builder": {
"session": "s-4f9a1c2b7d3e",
"harness": "claude",
"provider": "anthropic",
"family": "anthropic",
"model": "claude-fable-5-2"
},
"verifier": {
"harness": "codex",
"provider": "openai",
"family": "openai",
"model": "gpt-6-astra"
},
"verdict": {
"verdict": "pass",
"commit": "9f2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b2d4f6a"
},
"head": "9f2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b2d4f6a",
"stale": false,
"createdAt": "2026-11-14T09:12:43.870Z"
}
],
"nextCursor": null
}seats.list
- A plugin sends it to the host
- Permission
lists.read
One page of the seats in the daemon's fleet, in registry order: each installed harness, what it runs on and whether it is ready. The daemon's own seats.list method (cloud seats) is another list and is not exposed.
Params
cursoroptionalstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The nextCursor of the page before.
- matches
No other members are allowed.
Result
seatsrequiredarray; each item is object
seats[].harnessrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A registry id, the form tasks.create takes as builder and verifier.
- matches
seats[].providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
seats[].familyrequiredone of
"anthropic","openai","google","xai","moonshot","deepseek","local","auto"A provider family of the registry.
seats[].statusrequiredone of
"ready","degraded","missing"ready: it can take a task. degraded: installed, and something is in the way (an approval, a sign-in, a failed check). missing: not installed.
seats[].signInrequiredone of
"signed-in","signed-out","unknown"nextCursorrequiredstring or null
- matches
^[a-zA-Z0-9_-]{1,128}$
- matches
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example
{}{
"seats": [
{
"harness": "claude",
"provider": "anthropic",
"family": "anthropic",
"status": "ready",
"signIn": "signed-in"
},
{
"harness": "codex",
"provider": "openai",
"family": "openai",
"status": "degraded",
"signIn": "signed-in"
},
{
"harness": "grok",
"provider": "xai",
"family": "xai",
"status": "missing",
"signIn": "unknown"
}
],
"nextCursor": null
}storage.get
- A plugin sends it to the host
- Permission
storage.own
Reads one value from the plugin's own folder. value is null when the key holds nothing.
Params
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
No other members are allowed.
Result
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
valuerequiredstring or null
- at most 16,384 characters
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example 1
{
"key": "last-run"
}{
"key": "last-run",
"value": "2026-11-14T09:30:00.000Z"
}Example 2
{
"key": "never-set"
}{
"key": "never-set",
"value": null
}storage.set
- A plugin sends it to the host
- Permission
storage.own
Writes one value into the plugin's own folder, replacing what the key held. The value is on disk before the response is sent.
Params
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
valuerequiredstring
- at most 16,384 characters
Text of at most 16384 characters. The plugin chooses what it means; the host stores it as it is.
No other members are allowed.
Result
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
bytesrequiredinteger
- from 0 to 65,536
The size of the stored value in bytes of UTF-8.
No other members are allowed.
Errors
Besides the errors every call can get: quota-exceeded, unavailable.
Example
{
"key": "last-run",
"value": "2026-11-14T09:30:00.000Z"
}{
"key": "last-run",
"bytes": 24
}storage.delete
- A plugin sends it to the host
- Permission
storage.own
Removes one key. deleted is false when the key held nothing.
Params
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
No other members are allowed.
Result
keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
deletedrequiredboolean
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example
{
"key": "last-run"
}{
"key": "last-run",
"deleted": true
}storage.list
- A plugin sends it to the host
- Permission
storage.own
Every key the plugin's folder holds, sorted by key, with the size of each value and the total.
Params
None: params is an empty object.
Result
keysrequiredarray of objects
- at most 128 items
keys[].keyrequiredstring
- matches
^[a-z0-9][a-z0-9._-]{0,63}$
A name, never a path: 1 to 64 characters, lowercase letters, digits, dots, underscores and hyphens, starting with a letter or a digit.
- matches
keys[].bytesrequiredinteger
- from 0 to 65,536
bytesrequiredinteger
- from 0 to 1,048,576
The total of the values in bytes of UTF-8.
No other members are allowed.
Errors
Besides the errors every call can get: unavailable.
Example
{}{
"keys": [
{
"key": "last-run",
"bytes": 24
},
{
"key": "notes",
"bytes": 310
}
],
"bytes": 334
}tasks.create
- A plugin sends it to the host
- Permission
tasks.create
Asks the operator to start a task: a builder seat works in a new worktree of repo, a verifier seat of another provider family checks the result, and acceptance.command is the check. The host always puts the request in the daemon's approvals inbox, where the risk gate scores it; nothing starts unless the operator allows this very request in the app. The response comes when the operator answered and the daemon dispatched or refused the task, or when the request expired: up to 30 minutes for the answer and up to 15 more for the dispatch.
Params
reporequiredstring
- at most 4,096 characters
- matches
^(?:/(?!/)|[A-Za-z]:[\\/])[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
The git repository the task works in: a local absolute path (a drive path on Windows, never a share or a device path) without control or invisible formatting characters. It must be a repository or worktree the daemon already lists.
taskrequiredstring
- 1 to 4,000 characters
- matches
^(?:[\n\t]|[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}])*$ - does not match
^\s*$
What the builder is asked to do. 1 to 4000 characters, not only spaces. No control character other than line feed and tab, and no character that is invisible or reorders text: none of the Unicode categories Cc (but line feed and tab), Cf, Cs, Zl and Zp. So the operator reads exactly what was asked.
builderrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The harness id of the seat that builds.
- matches
verifierrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The harness id of the seat that verifies. The daemon refuses a verifier of the builder's provider family.
- matches
acceptancerequiredobject
- no other members
The check that decides whether the task is done. The daemon runs command in its own checkout of the result.
acceptance.namerequiredstring
- 1 to 64 characters
- matches
^[!-~](?:[ -~]*[!-~])?$
acceptance.commandrequiredstring
- 1 to 900 characters
- matches
^[ -~]*[!-~][ -~]*$
One line of 1 to 900 printable basic Latin characters (U+0020 to U+007E), not only spaces, so the operator reads exactly what will run.
nameoptionalstring
- matches
^[A-Za-z0-9][A-Za-z0-9 ._-]{0,47}$
A short name for the task; the worktree and the branch are named after it. Without it the daemon uses the first line of task.
- matches
baseoptionalstring
- matches
^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$
The branch the task starts from. Without it the daemon uses the branch of the repository's main worktree.
- matches
No other members are allowed.
Result
requestIdrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The inbox request the operator allowed.
- matches
lanerequiredstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The builder's session.
- matches
No other members are allowed.
Errors
Besides the errors every call can get: too-many-pending, approval-denied, approval-expired, task-refused, unavailable.
Example
{
"repo": "/srv/work/shop",
"task": "Add a test for the login redirect and make it pass.",
"builder": "claude",
"verifier": "codex",
"acceptance": {
"name": "unit tests",
"command": "npm test"
},
"name": "login redirect test"
}{
"requestId": "plugin_3b0c5a52-6d0f-4f0e-9d5c-1f2a3b4c5d6e",
"lane": "t-0c1d2e3f4a5b",
"session": "s-4f9a1c2b7d3e"
}Calls the host makes to a plugin
initialize
- The host sends it to a plugin
- No permission needed
The first message of every run. The host says which API it speaks and which permissions were approved; the plugin answers with the API version it was built for, which must equal the api of its manifest. Until the plugin has answered, the host refuses its requests with not-initialized.
Params
apirequiredstring
- matches
^[1-9][0-9]{0,3}\.(?:0|[1-9][0-9]{0,3})$
The host's API version.
- matches
hostVersionrequiredstring
- at most 64 characters
- matches
^[0-9A-Za-z][0-9A-Za-z.+-]*$
The daemon's version, for log lines only.
platformrequiredone of
"linux","macos","windows"pluginrequiredobject
- no other members
plugin.idrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
plugin.versionrequiredstring
- at most 64 characters
permissionsrequiredarray; each item is one of
"events.read","lists.read","commands.add","storage.own","tasks.create"- at most 5 items
- no item twice
The approved permission list, in the order of the permission table.
No other members are allowed.
Result
apirequiredstring
- matches
^[1-9][0-9]{0,3}\.(?:0|[1-9][0-9]{0,3})$
- matches
No other members are allowed.
Errors
The schema names none for it.
Example
{
"api": "1.0",
"hostVersion": "0.3.0",
"platform": "linux",
"plugin": {
"id": "night-shift",
"version": "1.2.0"
},
"permissions": [
"events.read",
"storage.own"
]
}{
"api": "1.0"
}ping
- The host sends it to a plugin
- No permission needed
Sent every 30 seconds. Any response or failure within 10 seconds shows the plugin is alive.
Params
None: params is an empty object.
Result
An empty object.
Errors
The schema names none for it.
Example
{}{}command.run
- The host sends it to a plugin
- Permission
commands.add
The operator chose one of the plugin's commands in the app. The plugin answers with the text the app shows. input is present exactly when the manifest gives the command input text.
Params
commandrequiredstring
- at most 32 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
A command's id inside its plugin, as the manifest gives it.
inputoptionalstring
- at most 1,024 characters
- matches
^[^\x00-\x1f\x7f]*$
The one line the operator typed: at most 1024 characters, no control characters.
No other members are allowed.
Result
textrequiredstring
- at most 16,384 characters
Plain text of at most 16384 characters. Before any client sees it the host cleans it: control characters other than line feed and tab are removed, invisible formatting characters are shown as marks, and the result is cut to 16384 bytes.
No other members are allowed.
Errors
The schema names none for it.
Example 1
{
"command": "summary"
}{
"text": "2 lanes verified since 21:00.\nt-0c1d2e3f4a5b passed on round 1."
}Example 2
{
"command": "note",
"input": "check the flaky login test"
}{
"text": "Saved."
}shutdown
- The host sends it to a plugin
- No permission needed
The host is about to stop the plugin. The plugin answers and exits. The host closes the plugin's input after this message, sends SIGTERM to what is left two seconds later and ends it by force three seconds after that.
Params
reasonrequiredone of
"daemon-stop","disabled","removed","updated"daemon-stop: the daemon is stopping. disabled: the operator switched the plugin off. removed: it is being removed. updated: the operator approved another copy than the one that runs, or the approved copy changed on disk.
No other members are allowed.
Result
An empty object.
Errors
The schema names none for it.
Example
{
"reason": "daemon-stop"
}{}What the host sends without being asked
event
- The host sends it to a plugin
- Permission
events.read
One event of a kind the plugin subscribed to.
Params
One event, in one of these shapes: session.start, session.end, turn.start, turn.end, approval.request, approval.resolved, lane.start, lane.stage, lane.verdict.
Errors
None: a notification has no answer.
Example
{
"kind": "turn.end",
"ts": "2026-11-14T09:31:07.412Z",
"seq": 58,
"session": "s-4f9a1c2b7d3e",
"data": {
"reason": "completed"
}
}events.dropped
- The host sends it to a plugin
- Permission
events.read
The plugin read too slowly: more than 1 MiB of messages waited for it, and the host left out the next count events. Sent once, as soon as there is room again and before any later event.
Params
countrequiredinteger
- at least 1
No other members are allowed.
Errors
None: a notification has no answer.
Example
{
"count": 12
}Event kinds
The params of an event notification, one shape per kind. ts is the time the daemon recorded the event; seq is the event's number in its ledger stream, so it rises within one session or within the lane stream and has gaps where the daemon recorded events a plugin does not get. A session event carries session, a lane event carries lane.
session.start
A session was created; its agent is about to start.
kindrequired"session.start"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
data.providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
data.transportrequiredone of
"acp","claude-pty-hooks","stream-json","muse-msp","omp-rpc"How the daemon drives the session's agent.
data.flagsrequiredobject
- no other members
What the session can do.
data.flags.interruptrequiredboolean
data.flags.steerrequiredboolean
data.flags.queuerequiredboolean
data.flags.forkrequiredboolean
data.flags.approvalRoutingrequiredboolean
data.flags.mcpPassthroughrequiredboolean
data.flags.usageReportingrequiredboolean
No other members are allowed.
{
"kind": "session.start",
"ts": "2026-11-14T09:12:44.031Z",
"seq": 1,
"session": "s-4f9a1c2b7d3e",
"data": {
"harness": "claude",
"provider": "anthropic",
"transport": "claude-pty-hooks",
"flags": {
"interrupt": true,
"steer": false,
"queue": false,
"fork": false,
"approvalRouting": true,
"mcpPassthrough": true,
"usageReporting": true
}
}
}session.end
The session's agent is gone. code is its exit status and signal the signal that ended it; each is null when there was none.
kindrequired"session.end"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.coderequiredinteger or null
data.signalrequiredstring or null
- matches
^SIG[A-Z0-9]{1,16}$
- matches
No other members are allowed.
{
"kind": "session.end",
"ts": "2026-11-14T09:31:07.590Z",
"seq": 60,
"session": "s-4f9a1c2b7d3e",
"data": {
"code": 0,
"signal": null
}
}turn.start
The agent was given a prompt. turn counts the session's turns from 1; mode is queue for a prompt the agent had queued itself; chars is the length of the prompt, whose text is never sent.
kindrequired"turn.start"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.turnrequiredinteger
- at least 1
data.moderequiredone of
"prompt","queue"data.charsrequiredinteger
- at least 0
No other members are allowed.
{
"kind": "turn.start",
"ts": "2026-11-14T09:12:44.040Z",
"seq": 2,
"session": "s-4f9a1c2b7d3e",
"data": {
"turn": 1,
"mode": "prompt",
"chars": 412
}
}turn.end
The turn ended, for the reason given.
kindrequired"turn.end"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.reasonrequiredone of
"completed","failed","interrupted","usage-limited"How a turn ended.
No other members are allowed.
{
"kind": "turn.end",
"ts": "2026-11-14T09:31:07.412Z",
"seq": 58,
"session": "s-4f9a1c2b7d3e",
"data": {
"reason": "completed"
}
}approval.request
The session's agent asked the operator for permission or asked a question, and waits. Only the session's own requests are delivered (see ownRequestId). action is the kind of thing it wants to do (read, edit, execute, question and so on) or tool when the agent gave no plain word; risk is the session's own rough label. What the request is about (paths, commands, the question) is never sent.
kindrequired"approval.request"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.requestIdrequiredstring
- at most 128 characters
- matches
^[a-zA-Z0-9_-]+-a[1-9][0-9]*$
A request the session's own agent made: the session id, then -a and a number, as Sessions.request() numbers them. Requests the daemon writes on a session for another reason (the cited checks of a rework, ids that start with check-) are never delivered.
data.actionrequiredstring
- matches
^[A-Za-z][A-Za-z0-9_.-]{0,63}$
- matches
data.riskrequiredone of
"low","medium","high"
No other members are allowed.
{
"kind": "approval.request",
"ts": "2026-11-14T09:14:02.300Z",
"seq": 17,
"session": "s-4f9a1c2b7d3e",
"data": {
"requestId": "s-4f9a1c2b7d3e-a1",
"action": "execute",
"risk": "high"
}
}approval.resolved
A request of the session was answered or withdrawn. delivered is false when the request was withdrawn without an answer reaching the agent (its turn ended, the daemon restarted).
kindrequired"approval.resolved"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
datarequiredobject
- no other members
data.requestIdrequiredstring
- at most 128 characters
- matches
^[a-zA-Z0-9_-]+-a[1-9][0-9]*$
A request the session's own agent made: the session id, then -a and a number, as Sessions.request() numbers them. Requests the daemon writes on a session for another reason (the cited checks of a rework, ids that start with check-) are never delivered.
data.decisionrequiredone of
"allow","deny","cancel"data.deliveredrequiredboolean
No other members are allowed.
{
"kind": "approval.resolved",
"ts": "2026-11-14T09:14:20.914Z",
"seq": 19,
"session": "s-4f9a1c2b7d3e",
"data": {
"requestId": "s-4f9a1c2b7d3e-a1",
"decision": "allow",
"delivered": true
}
}lane.start
The daemon dispatched a lane: a builder session started and a verifier seat was named.
kindrequired"lane.start"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
lanerequiredstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
datarequiredobject
- no other members
data.sessionrequiredstring
- matches
^[a-zA-Z0-9_-]{1,128}$
The builder's session.
- matches
data.builderrequiredobject
- no other members
The seat a lane was built or is verified on.
data.builder.harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
data.builder.providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
data.builder.familyrequiredone of
"anthropic","openai","google","xai","moonshot","deepseek","local","auto"A provider family of the registry.
data.builder.modelrequiredstring or null
- matches
^[A-Za-z0-9][A-Za-z0-9_.:/\[\]-]{0,127}$
A model name from a seat's approved arguments, or null when the seat names none or the name is not a plain model name.
- matches
data.verifierrequiredobject
- no other members
The seat a lane was built or is verified on.
data.verifier.harnessrequiredstring
- matches
^[A-Za-z0-9_.-]{1,128}$
A seat's harness id, as the registry names it.
- matches
data.verifier.providerrequiredstring
- matches
^[a-z][a-z0-9-]{0,63}$
A provider name of the registry, or unknown.
- matches
data.verifier.familyrequiredone of
"anthropic","openai","google","xai","moonshot","deepseek","local","auto"A provider family of the registry.
data.verifier.modelrequiredstring or null
- matches
^[A-Za-z0-9][A-Za-z0-9_.:/\[\]-]{0,127}$
A model name from a seat's approved arguments, or null when the seat names none or the name is not a plain model name.
- matches
No other members are allowed.
{
"kind": "lane.start",
"ts": "2026-11-14T09:12:43.870Z",
"seq": 311,
"lane": "t-0c1d2e3f4a5b",
"data": {
"session": "s-4f9a1c2b7d3e",
"builder": {
"harness": "claude",
"provider": "anthropic",
"family": "anthropic",
"model": "claude-fable-5-2"
},
"verifier": {
"harness": "codex",
"provider": "openai",
"family": "openai",
"model": "gpt-6-astra"
}
}
}lane.stage
The lane moved to another stage of the lane loop, or the daemon's reason for the stage it is in changed; so the same stage can arrive again with a later since. since is when the record was made. The daemon's reason text is never sent.
kindrequired"lane.stage"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
lanerequiredstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
datarequiredobject
- no other members
data.stagerequiredone of
"building","idle","checking","waiting-verifier","verifying","verify-failed","second-opinion","pass","rework","sent-back","cap","merging","merge-failed","merged","closed"Where a lane is in the daemon's lane loop.
data.sincerequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
No other members are allowed.
{
"kind": "lane.stage",
"ts": "2026-11-14T09:31:09.002Z",
"seq": 318,
"lane": "t-0c1d2e3f4a5b",
"data": {
"stage": "verifying",
"since": "2026-11-14T09:31:09.001Z"
}
}lane.verdict
The verifier's verdict on a commit of the lane was recorded. round counts the lane's verify rounds from 1; defects is how many defects a rework verdict cites (0 for a pass). The defects themselves are never sent.
kindrequired"lane.verdict"tsrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
seqrequiredinteger
- at least 1
lanerequiredstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
datarequiredobject
- no other members
data.commitrequiredstring
- matches
^(?:[0-9a-f]{40}|[0-9a-f]{64})$
A full git commit id.
- matches
data.verdictrequiredone of
"pass","rework"data.roundrequiredinteger
- at least 1
data.defectsrequiredinteger
- from 0 to 50
No other members are allowed.
{
"kind": "lane.verdict",
"ts": "2026-11-14T09:44:51.377Z",
"seq": 326,
"lane": "t-0c1d2e3f4a5b",
"data": {
"commit": "9f2c4e6a8b0d1f3a5c7e9b1d3f5a7c9e0b2d4f6a",
"verdict": "pass",
"round": 1,
"defects": 0
}
}Daemon methods that manage plugins
New methods on the daemon's own socket, for the app and for the omniconfluxd command line. They use the daemon's request and reply shapes, not the plugin wire: a failure carries only code and message.
plugin.install
- The app or the command line sends it to the daemon
Copies the folder at path into the host's store, checks the copy and leaves it waiting for approval. A copy of this id that is approved keeps running until the new copy is approved. When the copy is, file for file, the approved copy or the copy that already waits, nothing changes and unchanged is true.
Params
pathrequiredstring
- at most 4,096 characters
- matches
^(?:/|[A-Za-z]:[\\/]|\\\\)[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
An absolute path of this machine: /... on Linux and macOS, a drive or a share on Windows. No control or invisible formatting character (Unicode categories Cc, Cf, Cs, Zl, Zp).
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
versionrequiredstring
- at most 64 characters
snapshotHashrequiredstring
- matches
^[0-9a-f]{64}$
The hash of the copy that now waits (or, when unchanged, of the copy that was there).
- matches
staterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
replacedrequiredboolean
True when a copy that was waiting for approval was replaced by this one.
unchangedrequiredboolean
installedByrequiredone of
"operator","agent-session","plugin"Who called plugin.install, as the daemon saw the caller: operator (a process outside everything the daemon started), agent-session (inside a session or terminal holder the daemon started) or plugin (inside a plugin's container). Shown in the approval prompt.
No other members are allowed.
Errors
invalid-params, plugin-invalid, plugin-incompatible, plugin-limit, plugin-remove-pending, ledger-corrupt.
Example
{
"path": "/srv/work/night-shift"
}{
"plugin": "night-shift",
"version": "1.2.0",
"snapshotHash": "3b7e0c1d5a9f2468ace013579bdf02468ace13579bdf02468ace013579bdf0a1",
"state": "pending-approval",
"replaced": false,
"unchanged": false,
"installedBy": "operator"
}plugin.list
- The app or the command line sends it to the daemon
One page of the installed plugins sorted by id, as many as fit one frame.
Params
cursoroptionalstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Result
pluginsrequiredarray; each item is object
- at most 20 items
plugins[].pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
plugins[].namerequiredstring
- at most 64 characters
The display name, or the plugin id when no manifest can be shown.
plugins[].versionrequiredstring
- at most 64 characters
plugins[].descriptionrequiredstring
- at most 200 characters
plugins[].publisherrequiredstring or null
- at most 64 characters
plugins[].apirequiredstring
- matches
^[1-9][0-9]{0,3}\.(?:0|[1-9][0-9]{0,3})$
- matches
plugins[].staterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
plugins[].reasonrequiredone of
null,"first-approval","files-changed","store-changed","disabled","crash-limit","run-limit","api-unsupported","removing","remove-failed","exit","load-refused","stdout-closed","invalid-message","oversized-line","flood","refusals","memory","processes","initialize-timeout","initialize-failed","api-mismatch","unresponsive","spawn-failed"Why a plugin is not running, or null while it is starting or running. first-approval, files-changed, store-changed: with state pending-approval. disabled: with state disabled. crash-limit, run-limit: with state failed. api-unsupported: with state incompatible. removing (a removal is at work), remove-failed (it did not finish): with state remove-pending. A fault name: with state restarting.
plugins[].enabledrequiredboolean
False once the operator switched the plugin off.
plugins[].approvedrequiredboolean
True when the copy in the store is the one the operator approved, so it can run.
plugins[].waitingrequiredboolean
True when a copy waits for the operator's approval: a first install, an update, files that changed on disk, or an approval that was lost with the host's record.
plugins[].permissionsrequiredarray; each item is one of
"events.read","lists.read","commands.add","storage.own","tasks.create"- at most 5 items
- no item twice
plugins[].commandsrequiredinteger
- from 0 to 8
How many commands the manifest declares.
nextCursorrequiredstring or null
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Errors
Example
{}{
"plugins": [
{
"plugin": "night-shift",
"name": "Night Shift",
"version": "1.2.0",
"description": "Starts a follow-up task when a lane is verified.",
"publisher": "Example Works",
"api": "1.0",
"state": "pending-approval",
"reason": "first-approval",
"enabled": true,
"approved": false,
"waiting": true,
"permissions": [
"events.read",
"lists.read",
"commands.add",
"storage.own",
"tasks.create"
],
"commands": 2
}
],
"nextCursor": null
}plugin.get
- The app or the command line sends it to the daemon
Everything the app shows about one plugin: the approved manifest that is in force, the copy that waits for approval with the hash to approve and what differs, the lanes the plugin started, and the tail of its error output.
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
staterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
reasonrequiredone of
null,"first-approval","files-changed","store-changed","disabled","crash-limit","run-limit","api-unsupported","removing","remove-failed","exit","load-refused","stdout-closed","invalid-message","oversized-line","flood","refusals","memory","processes","initialize-timeout","initialize-failed","api-mismatch","unresponsive","spawn-failed"Why a plugin is not running, or null while it is starting or running. first-approval, files-changed, store-changed: with state pending-approval. disabled: with state disabled. crash-limit, run-limit: with state failed. api-unsupported: with state incompatible. removing (a removal is at work), remove-failed (it did not finish): with state remove-pending. A fault name: with state restarting.
enabledrequiredboolean
approvedrequiredboolean
manifestrequiredobject or null
The approved manifest, the one in force; null when nothing is approved.
approvalrequirednull or object
The approval in force, or null.
approval.snapshotHashrequiredstring
- matches
^[0-9a-f]{64}$
- matches
approval.operatorrequiredstring
- matches
^[a-zA-Z0-9 ._-]{1,64}$
- matches
approval.atrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
waitingrequirednull or object
The copy that waits for approval, or null.
waiting.kindrequiredone of
"install","files-changed","store-changed"install: a copy was installed and waits beside the approved one (or is the first). files-changed: the approved copy changed on disk. store-changed: the host's approval record failed its check, so nothing counts as approved.
waiting.snapshotHashrequiredstring
- matches
^[0-9a-f]{64}$
The hash of the copy that waits: the value plugin.approve takes.
- matches
waiting.manifestrequiredobject or null
The manifest of the copy that waits, as parsed; null when it is missing or not valid against manifest.schema.json (such a copy cannot be approved).
waiting.diffrequiredarray; each item is object or object
- 1 to 12 items
What differs between the approved copy and the copy that waits: one entry per manifest member that differs (before is null when there is no approval or the member was absent), then one entry with field files that counts every file that differs, the manifest file included. Never empty. An entry for id can appear only for a copy changed on disk or a record made again from the store's folders; such a copy names an id other than the plugin's and cannot be approved.
waiting.diff[].fieldrequiredShape 1 of 2
one of
"manifestVersion","api","id","name","version","description","publisher","main","permissions","commands"waiting.diff[].beforerequiredShape 1 of 2
any value
waiting.diff[].afterrequiredShape 1 of 2
any value
waiting.diff[].fieldrequiredShape 2 of 2
"files"waiting.diff[].addedrequiredShape 2 of 2
integer
- at least 0
waiting.diff[].removedrequiredShape 2 of 2
integer
- at least 0
waiting.diff[].changedrequiredShape 2 of 2
integer
- at least 0
waiting.diff[].pathsrequiredShape 2 of 2
array of strings
- at most 20 items
- each item: at most 200 characters
The first 20 paths that were added, removed or changed, sorted by path.
waiting.installedByrequiredone of
"operator","agent-session","plugin"or nullWho installed the copy that waits; null for files-changed and store-changed.
waiting.installedAtrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
When the copy was installed, or when the change was found.
- matches
filesrequiredobject
- no other members
The regular files of the approved copy.
files.countrequiredinteger
- at least 0
files.bytesrequiredinteger
- at least 0
storagerequiredobject
- no other members
storage.keysrequiredinteger
- from 0 to 128
storage.bytesrequiredinteger
- from 0 to 1,048,576
lanesrequiredarray; each item is string
- at most 50 items
- each item: matches
^t-[0-9a-f]{12}$
The lanes this plugin's task requests started that are at work (neither merged nor closed), newest first, at most 50, read from the plugin's own record in plugins.json, never from the ledger. A plugin installed again under the same id lists none of the old one's. The removal prompt lists them; a removal with stopTasks stops every lane of the plugin, also beyond these 50.
startingrequiredarray of strings
- at most 50 items
- no item twice
- each item: matches
^plugin_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
The request ids of the plugin's allowed task requests whose dispatch has not ended (at most 3 at a time) or was cut off by a daemon stop, newest first, at most 50. The removal prompt lists them with the lanes; a removal with stopTasks aborts every one.
crashesrequiredinteger
- at least 0
Crashes in the last 10 minutes.
lastExitrequirednull or object
How the plugin's last process ended, or null when it never ran or still runs its first.
lastExit.atrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
lastExit.coderequiredinteger or null
lastExit.signalrequiredstring or null
lastExit.faultrequiredone of
"exit","load-refused","stdout-closed","invalid-message","oversized-line","flood","refusals","memory","processes","initialize-timeout","initialize-failed","api-mismatch","unresponsive","spawn-failed"or nullnull when the host asked the plugin to stop.
stderrTailrequiredstring
- at most 4,096 characters
The plugin's error output, cleaned like every text of a plugin, and of that the last 4096 bytes, starting at a character boundary.
No other members are allowed.
Errors
invalid-params, plugin-not-found.
Example
{
"plugin": "night-shift"
}{
"plugin": "night-shift",
"state": "running",
"reason": null,
"enabled": true,
"approved": true,
"manifest": {
"manifestVersion": 1,
"api": "1.0",
"id": "night-shift",
"name": "Night Shift",
"version": "1.2.0",
"description": "Starts a follow-up task when a lane is verified.",
"main": "dist/main.mjs",
"permissions": [
"events.read",
"storage.own"
]
},
"approval": {
"snapshotHash": "3b7e0c1d5a9f2468ace013579bdf02468ace13579bdf02468ace013579bdf0a1",
"operator": "sam via app",
"at": "2026-11-14T08:02:11.000Z"
},
"waiting": {
"kind": "install",
"snapshotHash": "5d1f0a9c3e7b2468ace013579bdf02468ace13579bdf02468ace013579bdf0c4",
"manifest": {
"manifestVersion": 1,
"api": "1.0",
"id": "night-shift",
"name": "Night Shift",
"version": "1.2.1",
"description": "Starts a follow-up task when a lane is verified.",
"main": "dist/main.mjs",
"permissions": [
"events.read",
"lists.read",
"storage.own",
"tasks.create"
]
},
"diff": [
{
"field": "version",
"before": "1.2.0",
"after": "1.2.1"
},
{
"field": "permissions",
"before": [
"events.read",
"storage.own"
],
"after": [
"events.read",
"lists.read",
"storage.own",
"tasks.create"
]
},
{
"field": "files",
"added": 0,
"removed": 0,
"changed": 2,
"paths": [
"dist/main.mjs",
"omniconflux-plugin.json"
]
}
],
"installedBy": "agent-session",
"installedAt": "2026-11-14T09:58:40.120Z"
},
"files": {
"count": 14,
"bytes": 48211
},
"storage": {
"keys": 2,
"bytes": 334
},
"lanes": [
"t-0c1d2e3f4a5b"
],
"starting": [],
"crashes": 0,
"lastExit": null,
"stderrTail": ""
}plugin.approve
- The app or the command line sends it to the daemon
Records the operator's approval of the copy that waits, named by its hash together with the permission list and the command ids the prompt showed, then puts that copy in force and starts the plugin when it is enabled. Only the app can call it: the daemon refuses any caller that is not the operator's app (operator-only).
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
snapshotHashrequiredstring
- matches
^[0-9a-f]{64}$
- matches
permissionsrequiredarray; each item is one of
"events.read","lists.read","commands.add","storage.own","tasks.create"- at most 5 items
- no item twice
The permission list the prompt showed, in the order of the permission table, whatever order the manifest lists them in. The daemon compares it member by member with the waiting manifest's list put in that order; any difference is plugin-stale-snapshot.
commandsrequiredarray; each item is string
- at most 8 items
- no item twice
- each item: at most 32 characters, matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
The ids of the commands the prompt showed, in the manifest's order.
operatorrequiredstring
- matches
^[a-zA-Z0-9 ._-]{1,64}$
- matches
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
snapshotHashrequiredstring
- matches
^[0-9a-f]{64}$
- matches
approvedrequiredtruestaterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
No other members are allowed.
Errors
invalid-params, plugin-not-found, plugin-invalid, plugin-incompatible, plugin-stale-snapshot, plugin-remove-pending, operator-only, ledger-corrupt.
Example
{
"plugin": "night-shift",
"snapshotHash": "5d1f0a9c3e7b2468ace013579bdf02468ace13579bdf02468ace013579bdf0c4",
"permissions": [
"events.read",
"lists.read",
"storage.own",
"tasks.create"
],
"commands": [],
"operator": "sam via app"
}{
"plugin": "night-shift",
"snapshotHash": "5d1f0a9c3e7b2468ace013579bdf02468ace13579bdf02468ace013579bdf0c4",
"approved": true,
"state": "starting"
}plugin.discard
- The app or the command line sends it to the daemon
Deletes the installed copy that waits for approval and leaves the approved copy as it is. For a plugin that was never approved this removes the plugin. Refused for a caller inside anything the daemon started (operator-only).
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
discardedrequiredtrueremovedrequiredboolean
True when nothing of the plugin is left, because it was never approved.
No other members are allowed.
Errors
invalid-params, plugin-not-found, not-found, plugin-remove-pending, operator-only.
Example
{
"plugin": "night-shift"
}{
"plugin": "night-shift",
"discarded": true,
"removed": false
}plugin.enable
- The app or the command line sends it to the daemon
Switches a plugin on. An approved plugin starts; one that had failed is started again with its crash count cleared. Only the app can call it (operator-only).
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
enabledrequiredtruestaterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
No other members are allowed.
Errors
invalid-params, plugin-not-found, plugin-remove-pending, operator-only, ledger-corrupt.
Example
{
"plugin": "night-shift"
}{
"plugin": "night-shift",
"enabled": true,
"state": "starting"
}plugin.disable
- The app or the command line sends it to the daemon
Switches a plugin off: it is stopped and stays stopped across daemon restarts until it is enabled again. Its approval and its data stay.
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
enabledrequiredfalsestaterequiredone of
"pending-approval","disabled","starting","running","restarting","failed","incompatible","remove-pending"pending-approval: no approved copy can run. disabled: approved and switched off. starting: started, the first exchange is not finished. running. restarting: it crashed and the host waits to start it again. failed: the host gave up, or there was no room to run it. incompatible: its api version is not one this host runs. remove-pending: a removal is at work or did not finish; the plugin is off, is never started again, and the host finishes the removal by itself.
No other members are allowed.
Errors
invalid-params, plugin-not-found.
Example
{
"plugin": "night-shift"
}{
"plugin": "night-shift",
"enabled": false,
"state": "disabled"
}plugin.remove
- The app or the command line sends it to the daemon
Stops the plugin and deletes its copies, its approval, its data and its commands. With stopTasks true, which it is when absent, the tasks its requests started are stopped with it: every lane of its requests that is still at work is closed and its sessions stopped, and every dispatch still in flight, or cut off by a daemon stop, is aborted. They are read from the plugin's record in plugins.json, not from the ledger or a paged list. The reply is sent when its processes are gone and the folder is deleted. Refused for a caller inside anything the daemon started (operator-only). When a step fails, or an abort does not reply in time, the plugin is left remove-pending.
Params
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
stopTasksoptionalboolean
The operator's choice about the tasks the plugin's requests started. True, also when absent: they are stopped with the plugin. False: they go on as ordinary lanes.
No other members are allowed.
Result
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
removedrequiredtruestoppedrequiredinteger
- from 0 to 10,000
How many sessions the removal stopped.
worktreesrequiredarray; each item is string
- at most 50 items
- no item twice
- each item: at most 4,096 characters, matches
^(?:/|[A-Za-z]:[\\/]|\\\\)[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]*$
The worktrees that aborted dispatches left, those of the newest requests first, as many as fit one frame. They hold no plugin code and are kept for the operator.
No other members are allowed.
Errors
invalid-params, plugin-not-found, operator-only.
Example 1
{
"plugin": "night-shift",
"stopTasks": true
}{
"plugin": "night-shift",
"removed": true,
"stopped": 1,
"worktrees": []
}Example 2
{
"plugin": "night-shift"
}{
"plugin": "night-shift",
"removed": true,
"stopped": 0,
"worktrees": [
"/srv/work/shop-wt/login-redirect-test"
]
}plugin.commands
- The app or the command line sends it to the daemon
One page of the commands of every plugin whose state is starting, running, restarting or failed, sorted by plugin id and then by command id, as many as fit one frame. available is true while the command's plugin is running.
Params
cursoroptionalstring
- at most 97 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
A command as the app and the daemon name it: <plugin id>.<command id>.
No other members are allowed.
Result
commandsrequiredarray; each item is object
- at most 256 items
commands[].commandrequiredstring
- at most 97 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
A command as the app and the daemon name it: <plugin id>.<command id>.
commands[].pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
commands[].pluginNamerequiredstring
- at most 48 characters
The name in the plugin's approved manifest.
commands[].titlerequiredstring
- at most 48 characters
commands[].descriptionrequiredstring or null
- at most 120 characters
commands[].inputrequiredone of
"none","text"commands[].availablerequiredboolean
nextCursorrequiredstring or null
- at most 97 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
No other members are allowed.
Errors
Example
{}{
"commands": [
{
"command": "night-shift.note",
"plugin": "night-shift",
"pluginName": "Night Shift",
"title": "Add a note",
"description": null,
"input": "text",
"available": true
},
{
"command": "night-shift.summary",
"plugin": "night-shift",
"pluginName": "Night Shift",
"title": "Show tonight's summary",
"description": "Lists the lanes verified since the last run.",
"input": "none",
"available": true
}
],
"nextCursor": null
}plugin.command.run
- The app or the command line sends it to the daemon
Runs one plugin command because the operator chose it in the app, and returns the text to show. Only the app can call it (operator-only). input is required for a command whose manifest says input text and refused for any other.
Params
commandrequiredstring
- at most 97 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
A command as the app and the daemon name it: <plugin id>.<command id>.
inputoptionalstring
- at most 1,024 characters
- matches
^[^\x00-\x1f\x7f]*$
The one line the operator typed: at most 1024 characters, no control characters.
No other members are allowed.
Result
commandrequiredstring
- at most 97 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
A command as the app and the daemon name it: <plugin id>.<command id>.
textrequiredstring
- at most 16,384 characters
- matches
^(?:[\n\t]|[^\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}])*$
The plugin's text after the host cleaned it: no control character but line feed and tab, invisible formatting characters shown as marks, at most 16384 bytes.
No other members are allowed.
Errors
invalid-params, command-not-found, plugin-not-running, plugin-timeout, plugin-error, busy, operator-only.
Example
{
"command": "night-shift.summary"
}{
"command": "night-shift.summary",
"text": "2 lanes verified since 21:00.\nt-0c1d2e3f4a5b passed on round 1."
}plugin.request.get
- The app or the command line sends it to the daemon
The whole of a task request a plugin made, for the app's inbox card: the inbox item itself holds only a summary. Answering stays with approvals.answer.
Params
requestIdrequiredstring
- matches
^plugin_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
- matches
No other members are allowed.
Result
requestIdrequiredstring
- matches
^plugin_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
- matches
pluginrequiredstring
- 3 to 64 characters
- matches
^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$
pluginNamerequiredstring
- at most 64 characters
The name in the plugin's approved manifest, or the plugin id when the plugin is no longer installed.
callrequired"tasks.create"paramsrequired- no other members
digestrequiredstring
- matches
^[0-9a-f]{64}$
The SHA-256 of the request as the host stored it: the inbox item's scopeIdentity. The host dispatches exactly the stored request.
- matches
lastEventrequirednull or object
The kind, and the session or lane, of the last event the host delivered to the plugin before the request; null when it delivered none in this run. The card shows it, so the operator sees what the plugin reacted to.
lastEvent.kindrequiredone of
"session.start","session.end","turn.start","turn.end","approval.request","approval.resolved","lane.start","lane.stage","lane.verdict"lastEvent.sessionoptionalstring
- matches
^[a-zA-Z0-9_-]{1,128}$
A session id of the daemon.
- matches
lastEvent.laneoptionalstring
- matches
^t-[0-9a-f]{12}$
A lane id: the id of a task the daemon dispatched with a verifier.
- matches
staterequiredone of
"pending","answered","expired","orphaned"The inbox item's state.
createdAtrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
expiresAtrequiredstring
- matches
^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
UTC time in the form the daemon writes: YYYY-MM-DDTHH:MM:SS.mmmZ.
- matches
No other members are allowed.
Errors
Example
{
"requestId": "plugin_3b0c5a52-6d0f-4f0e-9d5c-1f2a3b4c5d6e"
}{
"requestId": "plugin_3b0c5a52-6d0f-4f0e-9d5c-1f2a3b4c5d6e",
"plugin": "night-shift",
"pluginName": "Night Shift",
"call": "tasks.create",
"params": {
"repo": "/srv/work/shop",
"task": "Add a test for the login redirect and make it pass.",
"builder": "claude",
"verifier": "codex",
"acceptance": {
"name": "unit tests",
"command": "npm test"
},
"name": "login redirect test"
},
"digest": "7c1e5b0f9a3d2468ace013579bdf02468ace13579bdf02468ace013579bdf0e9",
"lastEvent": {
"kind": "lane.verdict",
"lane": "t-0c1d2e3f4a5b"
},
"state": "pending",
"createdAt": "2026-11-14T09:45:00.000Z",
"expiresAt": "2026-11-14T10:15:00.000Z"
}Errors
A failure carries an error object: a code, one line of message for a person, and data for the three codes that name one. Decide by the code, never by the message, and treat a code you do not know as a failure of the call.
The error object the host sends
coderequiredone of
"invalid-request","method-not-found","invalid-params","permission-denied","not-initialized","busy","rate-limited","result-too-large","quota-exceeded","too-many-pending","approval-denied","approval-expired","task-refused","unavailable","shutting-down","internal-error"messagerequiredstring
- at most 500 characters
- matches
^[^\x00-\x1f\x7f]*$
One line the host wrote itself. It never carries a message of a lower layer: no path, no parser's words, no stack trace, no id of another session, no text from an agent.
dataoptionalobject
No other members are allowed.
- When
codeis"permission-denied",datais required anddatais an object withdata.permissiononly. - When
codeis"quota-exceeded",datais required anddatais an object withdata.limitonly. - When
codeis"task-refused",datais required anddatais an object withdata.reasononly. - Unless
codeis one of"permission-denied","quota-exceeded","task-refused",datais not allowed.
approval-denied
- The host sends it to a plugin
The operator did not allow the task.
Named by tasks.create.
approval-expired
- The host sends it to a plugin
The operator did not answer in 30 minutes.
Named by tasks.create.
busy
- The host sends it to a plugin
- A daemon method answers with it
16 requests of the plugin are unanswered.
From a daemon method: For plugin.command.run: 4 commands of the plugin are unanswered.
Any call a plugin makes can get it.
Named by plugin.command.run.
command-not-found
- A daemon method answers with it
No plugin in the state starting, running, restarting or failed has this command.
Named by plugin.command.run.
internal-error
- The host sends it to a plugin
Anything else. The message is always: The host could not complete the request.
Any call a plugin makes can get it.
invalid-params
- The host sends it to a plugin
- A daemon method answers with it
The params do not fit the call's schema, or name something the daemon does not know, such as a cursor.
From a daemon method: The params do not fit the method's schema, and an unknown member is refused. For plugin.command.run also: input given for a command that takes none, or missing for one that takes text.
Any call a plugin makes can get it.
Named by plugin.install, plugin.list, plugin.get, plugin.approve, plugin.discard, plugin.enable, plugin.disable, plugin.remove, plugin.commands, plugin.command.run, plugin.request.get.
{
"code": "invalid-params",
"message": "key must be 1 to 64 characters"
}invalid-request
- The host sends it to a plugin
The line is a request in name only: a member is missing, of the wrong type or unknown, or its id is in use.
Any call a plugin makes can get it.
ledger-corrupt
- A daemon method answers with it
The ledger is read-only, so nothing was installed, approved or switched on.
Named by plugin.install, plugin.approve, plugin.enable.
method-not-found
- The host sends it to a plugin
The host serves no such call.
Any call a plugin makes can get it.
{
"code": "method-not-found",
"message": "Unknown method: storage.clear"
}not-found
- A daemon method answers with it
For plugin.request.get: no such request. For plugin.discard: no installed copy waits.
Named by plugin.discard, plugin.request.get.
not-initialized
- The host sends it to a plugin
The plugin has not answered initialize yet.
Any call a plugin makes can get it.
operator-only
- A daemon method answers with it
The caller is not the operator's app. plugin.approve, plugin.enable, plugin.command.run and the answer to a plugin's request take only the app; plugin.remove and plugin.discard take no caller inside anything the daemon started.
Named by plugin.approve, plugin.discard, plugin.enable, plugin.remove, plugin.command.run.
permission-denied
- The host sends it to a plugin
The approved permission list lacks the call's permission. data.permission names it.
Any call a plugin makes can get it.
data
data.permissionrequiredone of
"events.read","lists.read","commands.add","storage.own","tasks.create"
No other members are allowed.
{
"code": "permission-denied",
"message": "This plugin was not approved for tasks.create",
"data": {
"permission": "tasks.create"
}
}plugin-error
- A daemon method answers with it
The plugin answered with a failure, or with a result that is not valid.
Named by plugin.command.run.
plugin-incompatible
- A daemon method answers with it
The plugin's api is not one this host runs. The message names both versions.
Named by plugin.install, plugin.approve.
plugin-invalid
- A daemon method answers with it
The folder or the installed copy fails a folder or manifest check. The message is the problem code of the first failed check, then what failed and where.
Named by plugin.install, plugin.approve.
plugin-limit
- A daemon method answers with it
20 plugins are installed.
Named by plugin.install.
plugin-not-found
- A daemon method answers with it
No plugin with this id is installed.
Named by plugin.get, plugin.approve, plugin.discard, plugin.enable, plugin.disable, plugin.remove.
plugin-not-running
- A daemon method answers with it
The command's plugin is not running, or it crashed before it answered.
Named by plugin.command.run.
plugin-remove-pending
- A daemon method answers with it
The plugin is being removed, or its removal did not finish.
Named by plugin.install, plugin.approve, plugin.discard, plugin.enable.
plugin-stale-snapshot
- A daemon method answers with it
No copy waits, or the copy that waits is not the one the call names by hash, permission list and command ids.
Named by plugin.approve.
plugin-timeout
- A daemon method answers with it
The plugin did not answer command.run in 30 seconds.
Named by plugin.command.run.
quota-exceeded
- The host sends it to a plugin
A storage limit was reached. data.limit says which: keys or bytes.
Named by storage.set.
data
data.limitrequiredone of
"keys","bytes"
No other members are allowed.
{
"code": "quota-exceeded",
"message": "The plugin's folder holds 128 keys",
"data": {
"limit": "keys"
}
}rate-limited
- The host sends it to a plugin
The plugin's budget of requests is empty (50 a second, 200 at once), or it made more than 10 list calls in the current second, or 20 task requests in 24 hours, or fewer than 3 of the 60 ledger records of the last hour are left for a task request.
Any call a plugin makes can get it.
{
"code": "rate-limited",
"message": "More than 50 requests a second for too long"
}result-too-large
- The host sends it to a plugin
The response would not fit on one line.
Any call a plugin makes can get it.
shutting-down
- The host sends it to a plugin
The plugin is being stopped.
Any call a plugin makes can get it.
task-refused
- The host sends it to a plugin
The daemon refused the allowed task, or the platform cannot run its check isolated. data.reason is the daemon's error code, or isolation-missing.
Named by tasks.create.
data
data.reasonrequiredstring
- at most 64 characters
- matches
^[a-z]+(?:-[a-z]+)*$
The daemon's own error code for the refused task.dispatch, or isolation-missing when the platform cannot run the task's check isolated.
No other members are allowed.
{
"code": "task-refused",
"message": "The daemon refused the task: registry-pending-approval",
"data": {
"reason": "registry-pending-approval"
}
}too-many-pending
- The host sends it to a plugin
3 task requests of the plugin are unanswered.
Named by tasks.create.
Product and company names in the values and examples on this page are trademarks of their owners. Naming one describes what the API carries, not endorsement.
Plugins ship inside the app
OmniConflux 0.3.0 is released for Omarchy, macOS on Apple silicon and Windows, in a closed beta with a waitlist. Plugins and the SDK ship inside the app from 0.3.0, and the SDK is not on a package registry.