# The kernel I/O contract

This page covers **script kernels**. For skills where an LLM does the work and calls your tools, see [LLM skills & tools](/skills/llm).

A script kernel is just a process. HUSK talks to it with streams, files, environment variables, and an exit code - nothing else. Any language that can do those four things is a first-class HUSK kernel.

## Input

How the request reaches your kernel depends on `input`:

| `input` | The kernel receives                                                           |
| ------- | ----------------------------------------------------------------------------- |
| `text`  | The request body on **stdin**.                                                |
| `file`  | A staged file path in **`$HUSK_INPUT_FILE`**. Any accompanying text on stdin. |
| `none`  | Nothing - stdin is empty.                                                     |

## Output

How HUSK builds the response depends on `output`:

| `output` | The kernel writes                                                               |
| -------- | ------------------------------------------------------------------------------- |
| `text`   | The result to **stdout**. Served as `text/plain`.                               |
| `json`   | JSON to **stdout**. Served as `application/json` (passed through untouched).    |
| `file`   | One file to **`$HUSK_OUTPUT_FILE`**, or many files into **`$HUSK_OUTPUT_DIR`**. |

For `file` output, stdout becomes an optional note (returned in the `x-husk-note` header for a single file). A single file is returned as a binary response with its MIME type; multiple files are returned as a JSON array of `{ filename, mime, size, dataBase64 }`.

## Status

| Exit code        | Meaning   | HTTP                                     |
| ---------------- | --------- | ---------------------------------------- |
| `0`              | success   | `200` with the output                    |
| non-zero         | failure   | `500` with `{ error, stderr, exitCode }` |
| killed (timeout) | timed out | `504`                                    |

A `file` skill that exits `0` but writes no file is treated as an error (`500`). For `text`/`json` skills, empty stdout is returned as-is - an empty `200` body (or `{}` for `json`).

## Environment variables

HUSK sets these for the kernel (the full server environment, including any API keys you set, is also inherited):

| Variable              | When           | Meaning                                       |
| --------------------- | -------------- | --------------------------------------------- |
| `HUSK_INPUT_FILE`     | `input: file`  | Path to the staged input file.                |
| `HUSK_INPUT_MIME`     | `input: file`  | The upload's content type, if known.          |
| `HUSK_INPUT_FILENAME` | `input: file`  | The upload's original filename, if provided.  |
| `HUSK_OUTPUT_FILE`    | `output: file` | Write a single file result here.              |
| `HUSK_OUTPUT_DIR`     | `output: file` | Write one or more file results into this dir. |

## Examples by shape

**text → text**

```bash
#!/bin/sh
exec tr 'a-z' 'A-Z'
```

**text → json**

```python
#!/usr/bin/env python3
import json, sys
print(json.dumps({"echo": sys.stdin.read().strip()}))
```

**file → file**

```bash
#!/usr/bin/env bash
set -euo pipefail
rembg i "$HUSK_INPUT_FILE" "$HUSK_OUTPUT_FILE"
echo "removed background"   # optional note
```

**none → text**

```bash
#!/bin/sh
date -u +%Y-%m-%dT%H:%M:%SZ
```

## How the kernel is run

The runner is deliberately strict:

* **No shell.** The command is spawned as an argv array (`shell: false`), so pipes, globs, `$VAR`, `&&`, and redirects in the command itself do nothing - put that logic inside the script.
* **Hard timeout.** A kernel past `timeout_ms` is killed with `SIGKILL`.
* **Bounded output.** Captured stdout/stderr is capped (5 MB) to protect the server.
* **Deterministic stdin.** stdin is always closed, so a kernel that reads stdin never blocks waiting for input that is not coming.
* **Cancellation.** If the HTTP client disconnects, the kernel is aborted.

:::warning
A `.sh` kernel must start with a shebang (`#!/usr/bin/env bash`) and be executable. HUSK marks local script files executable on load, but committing them with the executable bit set is best. Shebangs are not honored on Windows - name the interpreter explicitly in `run` (e.g. `run: bash run.sh`).
:::
