---
title: Other languages (ABI)
description: The ABI, for writing a package in any language.
---

The SDKs are conveniences. Any language that builds a WebAssembly module for **WASI preview 1** can write a function package by speaking **ABI 1** directly — C and C++ with wasi-sdk, Zig, AssemblyScript, TinyGo, Swift, a language of your own.

ABI 1 is small: the module is an [MCP](https://modelcontextprotocol.io) server over MCP's stdio transport, on the stateless revision `2026-07-28`, and it may import one host module for HTTP.

## The module

| Requirement | |
| --- | --- |
| Kind | A WebAssembly **core module** (not a component) |
| Target | WASI preview 1 — `wasm32-wasip1`, or `wasm32-wasi` in older toolchains |
| Exports | `_start`, taking and returning nothing (a WASI command), and its linear memory as `memory` |
| Imports | Only from `wasi_snapshot_preview1` and `subako_http` |
| Features | WebAssembly 2.0 without GC: bulk memory, reference types, multi-value, sign extension, non-trapping float-to-int, and 128-bit SIMD. Threads, shared memory, exceptions, tail calls, memory64, multi-memory, and relaxed SIMD are not available. |

Upload checks all of this, and names what it refused: an import Subako does not provide, a missing export, a component instead of a module.

## A call, from start to end

1. **Subako starts the module**

    It instantiates the module and calls `_start`, with `argv` of one entry
    and no environment variables. The first request is already waiting on
    stdin.

2. **The module answers each request**

    Requests arrive on **stdin**, one JSON-RPC 2.0 message per line. Answer
    each on **stdout**, as one line of compact JSON ending in `\n`, and flush.
    Subako sends one request at a time; answer it as it arrives, because
    stdin stays open until the last request is answered.

3. **Subako closes stdin**

    Once the last request is answered, stdin reaches end of file. The module
    must then exit with status 0 — return from `_start`, or `proc_exit(0)` —
    within one second.

**Stderr** is free for logs. Stdout is the protocol alone: any line on it that is not a JSON-RPC message breaks the call.

## The protocol

Every request carries the protocol revision and the client's capabilities in `_meta`. There is no `initialize` handshake.

```json
{"jsonrpc":"2.0","id":"call-0192f5c4","method":"tools/call","params":{"name":"greet","arguments":{"name":"Ada"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"subako","version":"0.2.3"},"io.modelcontextprotocol/clientCapabilities":{}}}}
```

Echo each request's `id` in its response. Ids are opaque — a string or a number — and differ every time.

### When a package is uploaded

Subako runs the module once to learn its tools, with 10 seconds to answer, no volume, no network, and only `/package` mounted.

1. `server/discover` — answer the revisions you speak and that you offer tools:

   ```json
   {"jsonrpc":"2.0","id":"discover-1","result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{}}}}
   ```

2. `tools/list` — answer every tool. A `nextCursor` asks for another page, up to 16 pages.

   ```json
   {"jsonrpc":"2.0","id":"tools-list-1","result":{"tools":[{"name":"greet","description":"Greet someone by name.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Who to greet."}},"required":["name"]}}]}}
   ```

3. Stdin closes; exit 0.

What `tools/list` answers is what the package offers, until its next version:

| | Rule |
| --- | --- |
| Tools | 1 to 64, with unique names |
| `name` | 1 to 64 characters: `a`–`z`, `0`–`9`, `_`, `-` |
| `description` | Up to 1,024 characters. The model reads it. |
| `inputSchema` | A JSON Schema with `"type": "object"` at the top, up to 64 KiB |
| `outputSchema` | Optional, up to 64 KiB. Shown to people, not the model. |
| All tools together | Up to 256 KiB |

### When the agent calls a tool

One `tools/call` with the tool's `name` and its `arguments`, an object. Answer with an MCP `CallToolResult`:

```json
{"jsonrpc":"2.0","id":"call-0192f5c4","result":{"content":[{"type":"text","text":"{\"message\":\"Hello, Ada!\"}"}],"structuredContent":{"message":"Hello, Ada!"}}}
```

Subako does not check `arguments` against your schema, so validate them yourself. A failure the model should read is a result with `isError`, never a JSON-RPC error:

```json
{"jsonrpc":"2.0","id":"call-0192f5c4","result":{"content":[{"type":"text","text":"name must not be empty"}],"isError":true}}
```

The content blocks Subako reads:

| Block | Shown to the model as |
| --- | --- |
| `{"type": "text", "text": ...}` | The text |
| `{"type": "image", "mimeType": "image/png", "data": "<base64>"}` | The image, counted against stdout's 1 MiB |
| `{"type": "resource_link", "uri": "volume:///tmp/chart.png", "name": "chart.png"}` | The image file at `/volume/tmp/chart.png`, read after the module exits |

A `volume:///` URI is the path beneath `/volume`, each segment percent-encoded. Images are PNG, JPEG, GIF, or WebP. Any other `resource_link` is shown as a line naming its link, and any other block as a note that it was left out.

### What breaks a call

The model is told the function crashed, with the last 4 KiB of stderr, when the module:

- answers with a JSON-RPC `error`, or a `resultType` other than `complete`;
- prints a line on stdout that is not a JSON-RPC message, or answers an id it was not asked;
- sends a request of its own — a stateless server sends none;
- exits before answering, exits non-zero, or does not exit within a second of stdin closing;
- traps, or runs out of memory or time.

Notifications — messages without an `id` — are ignored.

## Files

The [filesystem](/functions/runtime#the-filesystem) is a set of WASI preopens, in this order: `/volume/tmp`, then `/volume/data` when the agent enables it, then `/package` when the package has assets, then `/skills` and each `/skills/<name>`. Open paths by their absolute names.

`fd_allocate` answers `ENOTSUP`. Writes to files past the call's 1 GiB write budget answer `EDQUOT`. The `sock_*` functions are linked but never handed a socket: HTTP goes through `subako_http`.

## HTTP: `subako_http`

Two imports from the module `subako_http`, on `i32` values:

| Import | Does |
| --- | --- |
| `request(ptr, len) -> len` | Reads a JSON **request** of `len` bytes at `ptr`, sends it, holds the JSON **answer**, and returns its length. Blocks until the answer is in. |
| `read_response(ptr, len) -> 0` | Copies the held answer to `ptr`. `len` must be what `request` returned. Returns 0, and forgets the answer. |

A negative return is a misuse of the ABI: `-1`, a range outside the module's memory; `-2`, no answer held; `-3`, a length that does not match.

```c
__attribute__((import_module("subako_http"), import_name("request")))
int32_t subako_http_request(const uint8_t *ptr, int32_t len);

__attribute__((import_module("subako_http"), import_name("read_response")))
int32_t subako_http_read_response(uint8_t *ptr, int32_t len);
```

The request, up to 8 MiB, takes no fields but these:

```json
{
  "method": "POST",
  "url": "https://api.example.com/orders",
  "headers": { "accept": "application/json" },
  "body": { "type": "json", "json": { "order": 1042 } },
  "output": { "type": "inline" },
  "follow_redirects": true,
  "timeout_ms": 5000
}
```

| Field | |
| --- | --- |
| `method` | `GET` (the default), `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, or `OPTIONS` |
| `url` | Required. `https` only. |
| `headers` | Lowercase names. Connection headers such as `host` and `content-length` are dropped. |
| `body` | `{"type": "text", "text": ...}`, `{"type": "json", "json": ...}`, or `{"type": "file", "path": "/volume/tmp/in.bin"}` |
| `output` | `{"type": "inline"}` (the default), or `{"type": "file", "path": "/volume/tmp/out.bin"}` to save the body to the volume |
| `follow_redirects` | `true` by default, up to 10 hops |
| `timeout_ms` | Bounds the whole request. The call's own timeout applies either way. |

The answer is `{"ok": ...}` or `{"error": ...}`:

```json
{"ok":{"status":200,"headers":{"content-type":"application/json"},"body":{"type":"inline","text":"{\"status\":\"shipped\"}"}}}
{"ok":{"status":200,"headers":{},"body":{"type":"file","path":"/volume/tmp/out.bin","bytes":48211}}}
{"error":{"kind":"host_not_allowed","message":"api.example.com is not in this function's network allowlist"}}
```

An inline body is at most 256 KiB, and must be UTF-8 text; otherwise the request fails with `too_large` or `not_text`, and should save to a file instead. The error kinds are `invalid_request`, `host_not_allowed`, `forbidden_destination`, `credential_header`, `credential_unavailable`, `too_many_redirects`, `timeout`, `too_large`, `not_text`, `write_budget`, `bad_path`, `connection`, and `abi`.

## Testing locally

A module can be tried on your machine in any runtime for WASI preview 1 commands: give it one request on stdin and read its answer on stdout.

```bash
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"greet","arguments":{"name":"Ada"}}}'
```

The runtime must provide `subako_http` for the module to load. The Rust and Go templates ship `tests/subako_http_stub.wat`, a stub whose every request fails, for runtimes that can link one module into another:

```wat tests/subako_http_stub.wat
(module
  (func (export "request") (param i32 i32) (result i32) i32.const -1)
  (func (export "read_response") (param i32 i32) (result i32) i32.const -2))
```

## Checklist

1. Build a WASI preview 1 command module exporting `_start` and `memory`, without threads or exceptions.
2. Read stdin a line at a time; at end of file, exit 0.
3. Ignore messages without an `id`.
4. Answer `server/discover` with `"supportedVersions": ["2026-07-28"]` and a `tools` capability.
5. Answer `tools/list` with your tools and their object input schemas.
6. Answer `tools/call` with a `CallToolResult`, using `isError` for failures.
7. Write one compact JSON line per answer to stdout, and flush. Log to stderr.
8. Read and write files under `/volume/tmp`, `/volume/data`, `/package`, and `/skills`.
9. Send HTTP through `subako_http`.

Then pack `module.wasm` with a [`subako-package.json`](/functions/packages#the-manifest) whose `abi` is `1`.
