A running Mica program can answer questions about itself. Started with the runtime flag rtp=listen, it opens a socket of its own, and a client, such as the editor or a command-line tool, connects and asks. The protocol is to a running program what the Language Server Protocol is to source code: JSON-RPC 2.0 messages, framed the same way, in a session that opens with initialize.

./server +mica rtp=suspend -mica --port 8080
mica runtime protocol: the program waits for 'runtime/continue' on /run/user/1000/mica/48213.sock

A program started without an rtp= flag has no agent at all: no thread, no socket, and nothing checked while it runs.

This page is protocol version 1. The version changes when a method, a member or a code changes meaning or disappears; methods and members are added within a version, and the answer to initialize lists the methods a program offers.

The transport

RuleWhat it means
a Unix domain socket per programonly clients on the same machine connect; a client on another machine reaches a program through SSH forwarding of the socket
owner-onlythe socket file allows only its owner (mode 0600), the directory of the default path allows only its owner (mode 0700), and a connecting client whose user differs from the program’s is closed before anything is read
the default path/run/user/<uid>/mica/<pid>.sock when the system provides the user’s runtime directory, /tmp/mica-<uid>/<pid>.sock otherwise, so a client finds every program of its user in two directories; no environment variable is read
a named pathrtp=<path> listens at path instead; a socket left there by a program that ended without removing it is replaced, and a path another program listens on, or a file that is not a socket, is refused
removed at exitthe program removes its socket when it ends
one client at a timea second client waits until the first leaves, and is then served a session of its own
the program undisturbedthe agent is one thread outside the program’s carriers, blocks every signal so the program’s own handlers receive them, and allocates no memory of the program’s

Framing

Every message, in both directions, is a header block and a body, exactly as the Language Server Protocol frames them. Each header line ends with a carriage return and a line feed, and an empty line ends the block:

Content-Length: 76

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1}}
HeaderRule
Content-Lengthrequired: the body’s size in bytes, in decimal digits, at most 65536
Content-Typeaccepted and not read

Header names are compared without regard to case, and each is written as the name, a colon, one space and the value. A frame that breaks these rules, whether with a header of another name, a header named twice, a missing or malformed length, a body larger than the bound or a header block longer than 1024 bytes, ends the session without an answer, because the client’s next frame can no longer be found. The client may connect again.

The body is JSON as RFC 8259 defines it, read strictly: well-formed UTF-8, no comments, no trailing commas, no key named twice in one object (judged by the keys’ text, so a key spelled with an escape and the same key spelled without one are the same key), and no escaped NUL character in a string.

The session

A client’s first request is initialize. Any other request before it is answered with the error -32002, and a second initialize in the same session with -32600. Each connection is a session of its own, so a client that reconnects initializes again.

A request carries jsonrpc (the string "2.0"), method, an id, and optionally params, an object or an array; no other member is accepted. The id is an integer, a string of at most 255 bytes, or null, and the answer repeats it. A message without an id is a notification: it is carried out and never answered, not even when it is refused. A batch, a JSON array of messages, is not accepted.

Methods

initialize

Opens the session.

ParameterTypeMeaning
protocolVersionintegerthe protocol version the client speaks; required

The result:

{"protocolVersion":1,"runtime":{"name":"mica-runtime","processId":48213,"suspended":true},"methods":["initialize","runtime/manifest","runtime/configuration","runtime/snapshot","runtime/suspend","runtime/continue","tasks/tree","tasks/cancel","tasks/subscribe","tasks/unsubscribe","memory/regions","memory/blocks","memory/types"],"notifications":["tasks/changed"]}
MemberMeaning
protocolVersionthe protocol version the program speaks
runtime.namealways mica-runtime
runtime.processIdthe program’s process identifier
runtime.suspendedwhether the program is held before its first statement, waiting for runtime/continue
methodsevery method the program offers, in this page’s order
notificationsevery notification the program may send a subscribed client

A version the program does not speak is answered with -32602, reason protocol-version, and the versions it does speak in data.supported.

runtime/manifest

The program’s manifest, the document the compiler wrote into the program file, as mica --inspect reads it; null for a program linked without one. It takes no parameters.

runtime/configuration

What the program’s start resolved, the configuration member of the answer to the info runtime flag, with one member only a running program knows: carriers.started, the carriers its pool has started, its main thread among them, 0 while the pool has not started, and 1 for a single-carrier program. It counts the pool where a count of the process’s threads cannot, under an emulator, whose own threads are the process’s as well. It takes no parameters.

{"carriers":{"effective":2,"source":"build","oversubscribed":false,"started":2},"processes":{"effective":8,"source":"processors","oversubscribed":false},"stdout":"default"}

tasks/tree

The tasks the scheduler holds, read while the program holds still (see reading a running program); null for a program without tasks. It takes no parameters.

{"flavor":"multicore","carriers":2,"complete":true,"tasks":[{"ordinal":0,"id":0,"kind":"main","state":"ready","parent":-1,"cancelled":false},{"ordinal":1,"id":3,"kind":"task","state":"running","parent":0,"cancelled":false},{"ordinal":2,"id":1,"kind":"task","state":"parked","parent":0,"cancelled":false}]}
MemberMeaning
flavorsingle or multicore, the program’s tasking flavor
carriersthe carriers of a multicore program’s pool; 0 before the pool starts, at the program’s first task or suspension
completefalse when the list names fewer tasks than the scheduler holds: at most 256 are named, and fewer when the answer would pass its bound
tasks[].ordinalthe task’s place in this answer; ordinals may name other tasks in the next answer
tasks[].idthe task’s identity for its whole life: 0 for the main body, then one number per task and generator in the order they begin; tasks/cancel names a task by it
tasks[].kindmain for the program’s main body, task, or generator
tasks[].staterunning on a carrier, ready to run, parked waiting on a descriptor or a timer, suspended for a generator waiting between two pulls, finished
tasks[].parentthe ordinal of the task that owns the scope the task was started in, or -1
tasks[].cancelledwhether a cancellation is pending for the task
tasks[].owned_scopefor a task that owns a scope: live_children, error (the first failure’s code, 0 for none) and timed_out
tasks[].generatorfor a generator: started, exhausted, closing, and holder, the ordinal of the activation that holds the instance between two pulls, or -1 for a stream’s producer, which the stream’s ring holds, and for a holder the list does not name

A generator its caller holds between two pulls is named with the state suspended, and its generator.holder names the activation that holds it.

tasks/cancel

Cancels the scope a task was started in, through the scheduler’s own cancellation: the task and every task started beside it stop, and every cleanup of theirs runs, exactly as when the scope’s block is left early. The task that owns the scope is not cancelled; its join completes once the scope’s tasks have stopped.

ParameterTypeMeaning
taskintegerthe identity tasks/tree shows as id; required
{"cancelled":true,"live":3}

cancelled is true when this request cancelled the scope and false when a cancellation of it was already under way; live is the number of tasks the scope held. Tasks that are running or ready stop at their next suspension point, and a task waiting on a descriptor or a timer is woken by the scope’s owner once the owner waits in its join, at the end of the scope’s block. The main body was started in no scope and is refused with reason main-body; an identity no living task has, with unknown-task.

tasks/subscribe

Starts the notification tasks/changed, sent at most once per interval and only when tasks began or ended since the last one. A second subscription changes the interval. A program counts its tasks only while a client is subscribed.

ParameterTypeMeaning
intervalMillisecondsintegerthe shortest time between two notifications, from 50 to 60000; required
{"subscribed":true}

The notification:

{"jsonrpc":"2.0","method":"tasks/changed","params":{"begun":3,"ended":0}}

begun and ended count the tasks, a channel’s producers among them, that began and ended since the last notification, so a client that missed none can add them up. The subscription ends with tasks/unsubscribe or with the session.

tasks/unsubscribe

Ends the subscription. It takes no parameters.

{"subscribed":true}

subscribed is true when there was a subscription to end.

runtime/snapshot

The runtime’s whole description of itself, read while the program holds still: the same document a debugger receives from the editor’s runtime view. It takes no parameters.

{"mica_narrator":1,"tasking":{...},"tape":null}

tasking is the answer to tasks/tree, and tape the automatic differentiation tape’s summary, or null for a program without one.

runtime/suspend

Stops the program until runtime/continue, or until the client that suspended it leaves. Readings taken while it is suspended all describe the same instant. It takes no parameters.

{"suspended":true}

suspended is true when this request suspended the program and false when it was suspended already.

runtime/continue

Releases a program held before its first statement by rtp=suspend, or suspended by runtime/suspend. It takes no parameters.

{"released":true}

released is true when this request released the program and false when the program was neither held nor suspended.

memory/regions

What the program’s memory holds, at the coarsest grain: every live region with its blocks and its bytes, the deployment class the program was built for, the arena where it has one, and the explicit cells it holds. Read while the program holds still (see reading a running program). It takes no parameters.

{"sites":true,"class":"hosted-heap","arena":null,"explicit":{"cells":3,"bytes":96},"owners":[{"owner":"program","task":-1,"regions":[{"ordinal":0,"blocks":1,"dedicated":0,"capacity":65552,"used":1184}]}],"complete":true}
MemberMeaning
siteswhether the program’s cells carry their allocating site, which only a debug-tier program’s do
classthe deployment class: hosted-heap or fixed-arena
arenathe fixed arena’s size and used bytes, or null for a program on the hosted heap
explicit.cells, explicit.bytesthe cells new made outside any region, and their bytes
owners[].ownerprogram for the regions every task shares, task for one task’s own partition, shared for the one partition every task of a program without the multicore runtime allocates from
owners[].taskthe task’s identity, or -1 where the partition belongs to no single task
owners[].regions[]each region’s ordinal, its blocks and dedicated block counts, and the capacity and used bytes of both together
completefalse when the program holds more regions than the answer names

Every tier answers this reading: a region’s blocks and its bump are the region’s own geometry, which needs no header.

memory/blocks

Every block of every live region, walked cell by cell. Read while the program holds still. It takes no parameters.

{"sites":true,"blocks":[{"owner":"program","task":-1,"region":0,"kind":"regular","capacity":65552,"used":1184,"cells":[{"offset":0,"bytes":32,"state":"live","type":"Ledger","file":"/home/mica/Ledger.mica","line":79,"column":15},{"offset":32,"bytes":528,"state":"live"},{"offset":560,"bytes":128,"state":"free"},{"offset":688,"bytes":128,"state":"live"},{"offset":816,"bytes":368,"state":"table"}]}],"complete":true}
MemberMeaning
blocks[].kindregular for a block the region bumps in, dedicated for one holding a single oversize cell
blocks[].capacity, blocks[].usedthe block’s payload bytes, and the bytes its cells occupy
cells[].offset, cells[].byteswhere the cell stands in the block’s payload, and how far the next cell stands, its header included
cells[].statelive for a value’s storage, free for a cell released onto its class’s free list, table for the region’s own table of those lists
cells[].typethe allocated type’s name, absent for a cell made by code compiled at another tier
cells[].file, line, columnwhere the allocation stands in the program’s source, absent with the type
completefalse when a block holds more cells than the answer names, or a walk met a cell its span could not confirm

The walk is the cell header’s own: each cell says how far the next one stands. A program built at a release tier carries no header, and this reading is refused with -32600, reason needs-debug.

memory/types

The program’s cells summed by the type they hold: every live region cell and every explicit cell, released cells left out. Read while the program holds still. It takes no parameters.

{"sites":true,"types":[{"cells":1,"bytes":32,"type":"Ledger","file":"/home/mica/Ledger.mica","line":79,"column":15},{"cells":3,"bytes":96,"type":"Node"},{"cells":2,"bytes":656}],"complete":true}
MemberMeaning
types[].typethe allocated type’s name, absent for cells made by code compiled at another tier
types[].cells, types[].byteshow many cells of the type the program holds, and their bytes
types[].file, line, columnwhere the allocation stands, written only where one allocation in the program makes this type; a type made in several places carries its name alone
completefalse when the program holds more types than the answer names

The entries are ordered by the type’s name, the nameless entry last, so one program’s reading is one document however its cells were met. Like memory/blocks, this reading needs the debug tier’s header and is otherwise refused with reason needs-debug.

Reading a running program

tasks/tree, tasks/cancel, runtime/snapshot, runtime/suspend and the three memory/ readings stop the program while they read, the way a debugger stops it: every thread of the program is sent the signal SIGURG, and each waits in the signal’s handler until the reading is done. A reading holds the program for a few milliseconds: the time its threads take to stop and the answer takes to render. Because a signal reaches a thread wherever it is, a program holds still for a reading even inside a long loop or a C call.

What a program may noticeWhy
a system call that the kernel does not restart returns early with EINTRthe signal interrupts it; reads and writes are restarted, while a sleep or a wait with a timeout may return early, as under Go’s preemption signal
a SIGURG handler the program installed before the agent started still receives every SIGURG that is not the agent’sthe agent passes them on
a SIGURG handler the program installs after the agent started replaces the agent’sthe readings then fail with stop-timeout

Errors

A refused request is answered with a JSON-RPC error. Its data.reason names the refusal in one word a client can act on.

{"jsonrpc":"2.0","id":7,"error":{"code":-32600,"message":"the message is not a request the protocol accepts","data":{"reason":"member"}}}
CodeMessageReasons
-32700the message is not well-formed JSONjson
-32600the message is not a request the protocol acceptsbatch, not-object, jsonrpc-version, id, method, params, member, initialized-already, needs-debug
-32601the runtime offers no such methodmethod
-32602the parameters do not fit the methodparameter, protocol-version, unknown-task, main-body, no-tasks
-32603the runtime could not answeranswer-too-large, stop-timeout
-32002the session is not initializednot-initialized
ReasonThe request
jsonis not JSON the protocol reads; data.detail says why and data.position is the byte offset in the body where reading stopped
batchis a JSON array
not-objectis a JSON value other than an object
jsonrpc-versionhas no jsonrpc member, or one that is not "2.0"
idhas an id that is not an integer, a string within the bound, or null; the answer’s id is null
methodhas no method string, or names a method the program does not offer
paramshas params that are neither an object nor an array
memberholds a member JSON-RPC does not define
initialized-alreadyis an initialize in a session that is open already
not-initializedcame before initialize
parameterlacks a parameter the method needs, or has one of another type
protocol-versionnames a protocol version the program does not speak; data.supported lists those it does
answer-too-largewould be answered with more than 65536 bytes
needs-debugasks for a reading that needs the cell headers only a program built at the debug tier carries
stop-timeoutneeded the program to hold still, and a thread of it did not stop within 2 seconds
unknown-tasknames a task identity no living task has
main-bodyasks to cancel the scope of the main body, which was started in no scope
no-tasksasks about tasks in a program built without the task runtime

A body that is not JSON, and a request whose id cannot be read, are answered with "id":null.

When the agent cannot start

The start ends with one line on standard error and exit status 2, before any of the program runs:

mica runtime flags: the runtime protocol could not start on '<path>': <reason>
Reason
the socket path is longer than a Unix domain socket address holds
the socket directory is not a directory only this user can enter
the socket directory could not be created
another process listens on the socket path, or the path is not a socket
the socket could not be opened
the agent thread could not be started

A reason that comes from the operating system is followed by its own words, for example : Permission denied.

Bounds

BoundValue
a request body65536 bytes
a header block1024 bytes
the tokens of a request’s JSON1024
the nesting of a request’s JSON16 containers
a string id255 bytes
an answer body65536 bytes
the tasks one answer names256
the wait for a program to hold still2 seconds
a send to a client that does not read1 second, after which the session ends
clients the listen queue holds while one is served4, and the kernel may admit one more