Your AI chat gets a real computer of its own.
Files, a browser, and somewhere to run code. It can build things, look stuff up online and keep your work as you go. Turn a chat you already had into a bot that does the job again tomorrow.
claude mcp add husk -- npx -y @husk-ai/mcpOne command. Nothing else to edit.
An AI chat panel with a short exchange in it, wired by a cable to a small computer below, whose port is lit.
$ husk up demo --provider local ✓ demo is up provider local (WSL2 (Ubuntu)) isolation guardrails only — not a sandbox workdir /work $ husk exec demo -- 'uname -sr; echo hello > /work/note.txt; cat /work/note.txt' Linux 6.18.33.2-microsoft-standard-WSL2 hello $ husk exec demo -- 'sudo rm -rf /' error refused: privilege escalation hint: add a pattern to guardrails.allowCommands in husk.yaml if this is intentional
That ran on a Windows laptop with no Docker and no API key. The local provider found WSL2 and gave it a real kernel and a real /work. Then it refused a command that would not have been recoverable, and said what to change if the refusal was wrong.
youclaude mcp add husk -- npx -y @husk-ai/mcp
the conversation now has
- shellrun a command, get stdout, stderr and an exit code
- read_fileread a path inside the workspace
- write_filewrite a path inside the workspace
- expose_portpublish a port the agent started listening on
$ husk exec demo -- 'uname -sr; echo hello > /work/note.txt; cat /work/note.txt'Linux 6.18.33.2-microsoft-standard-WSL2hello
a computer, for the rest of the conversation
what it does
Husk does two things.
One binary. The first job hands a machine to an agent that does not have one. The second takes a conversation you already finished and keeps it running.
It gives an agent a computer.
A computer is a disposable Linux machine: shell, filesystem, ports, snapshots. Nothing is spun up until a tool actually needs one, and a stable key maps a conversation to the same machine, so its files survive across tool calls without you tracking ids.
A cold docker version takes about 800 ms, so provider probes are cached for 30 seconds. An agent that creates four machines in a row pays that once.
$ npx -y @husk-ai/cli husk 0.1.0 · first run · ~/.husk created computer providers docker unavailable the daemon is not reachable podman not found local ready guarded working directory ~/.husk/workspaces -- guardrails, not a sandbox model providers ollama ready gemma3, qwen2.5-coder anthropic no key set ANTHROPIC_API_KEY selected local + ollama/gemma3 free, on this machine, no account no telemetry. nothing left this machine.
$ husk import # find your transcripts $ husk distill tr_01hxyz --out triage.yaml # chat -> agent spec $ husk run triage.yaml "check the build" $ husk serve # http, discord, cron, cli
It turns a chat you already had into a bot.
Husk reads a Claude Code, ChatGPT or Cursor transcript, walks parentUuid back from the last leaf to get the conversation as it actually ran rather than every dead end, and writes a husk.yaml you can diff.
The distiller runs with no API key. It mines the instructions you kept repeating, the answers you did not correct, and the tools you actually used, then reports what it could not determine instead of inventing it.
the whole install
One line gives your client a machine.
claude mcp add husk -- npx -y @husk-ai/mcpOne command. Nothing else to edit.
In Claude Code and Codex there is no second step and no config file to edit. Cursor, Zed and Antigravity have no add command of their own, so those get the block to paste and the path to paste it into — the tab above switches between them. Either way the client gets twenty tools against a real Linux machine — a shell, the filesystem, ports, and a browser — and that filesystem persists for the rest of the conversation.
The first tool result tells the model how isolated it is, because a model that believes it is contained when it is not will take risks it otherwise would not.
the computer
- shellrun a command, get stdout, stderr and an exit code
- read_fileread a path inside the workspace
- write_filewrite a path inside the workspace
- edit_filereplace an exact string, and fail loudly if it is missing or ambiguous
- list_dirlist a directory
- expose_portpublish a port the agent started listening on
- computer_infokernel, cpus, memory, disk and network, so the model stops probing
the browser
- browser_gotoopen a URL in a real Chromium and return the rendered text
- browser_snapshotlist everything readable or clickable, each with a ref
- browser_clickclick an element the snapshot named
- browser_typetype into a field
- browser_screenshottake a PNG, for checking a layout or showing a human
13 in all. A real Chromium inside the computer, driven by structured page text rather than pixels — which is why an agent can use it without seeing.
after the one line
Three things to ask it first.
The install is one line and then nothing tells you what changed. These are the three that show you the computer is real, in the order that makes the point fastest.
“What kernel are you on?”
It runs uname -sr in the machine and reads it back. The answer is Linux on a computer that did not exist a second ago, and it is not one the model could have guessed.
“Write a note to /work/note.txt, then read it back next turn.”
The filesystem persists for the rest of the conversation. A stable key maps the chat to the same machine, so nothing is tracked by you.
“Start a web server and give me the URL.”
expose_port publishes the port it started listening on and hands back a URL you can open. That is the moment the computer stops being an abstraction.
containment
husk doctor tells you which one you have.
Two of the five providers are not isolated in any meaningful sense. Availability.isolated is on the provider interface so that no part of this product ever has to be vague about which two.
| provider | isolation | mechanism | cost | when it wins |
|---|---|---|---|---|
| docker | kernel | namespaces, cgroups, seccomp, --cap-drop ALL, read-only root, uid 1000 | free, local | the default when the daemon is up |
| podman | kernel | the same boundary, rootless | free, local | Linux without a Docker daemon |
| local | guardrails only | path jail through realpath, scrubbed credentials, deny list, output caps, process-tree kill | free, local | nothing else is available |
| ssh | whatever the remote is | the box you pointed it at — Husk cannot know and does not guess | free if you own the box | an Oracle Always Free ARM instance, a Pi, a VPS |
| fly | microVM | a Firecracker machine, not a container on your kernel | metered | bursty parallel work, no local resources |
Husk never silently substitutes a weaker provider for the one you asked for. Asking for --provider docker with the daemon down is an error, not a downgrade.
- provider
- docker
- isolation
- kernel
- cost
- free, local
- when it wins
- the default when the daemon is up
namespaces, cgroups, seccomp, --cap-drop ALL, read-only root, uid 1000. Drawn without WebGL — the shape carries the same reading.
guardrails, not a sandboxThe local provider pins the working directory, resolves every path through realpath and refuses escapes, strips credential-shaped environment variables, caps output, and kills the process tree on timeout. That stops accidents. It will not stop an adversary, and a prompt-injected model is closer to an adversary than to an accident.
the unit of value
What comes out is a file you can read.
Deliberate key order, block scalars, and a provenance header naming the transcript it came from. The file is optimised for review rather than for machines, because the first thing you will do with it is disagree with a line and change it.
Distil one, edit it, then run it on the CLI or serve it over HTTP, Discord, Slack or cron. The same file runs against Opus or against a local Gemma by changing one word.
# Distilled from a Claude Code session where I kept asking the same
# questions about red builds. Most of the persona is text I had already
# typed into the chat four separate times.
apiVersion: husk/v1
name: ci-triage
model: sonnet
# Falls through to a local model rather than failing when the key is
# rate limited.
fallbackModels: [flash, gemma]
persona: |
You triage CI failures for a TypeScript monorepo.
Always read the failing job's log before forming an opinion. Quote the
first line that actually failed -- not the last line, which is usually
the runner giving up.
Never rerun a job more than once. If it fails twice the same way, it is
not a flake.
tools: [computer, files]
computer:
flavor: node
network:
mode: egress
allow: ['*.github.com']
idleTimeoutSec: 600
limits: { maxSteps: 24, maxCostUsd: 0.25 }
triggers:
- { type: http }
- { type: cron, schedule: '*/15 * * * *', prompt: 'any red builds?' }
cost
The free path is the same path everything else is built on.
The local provider is the primitive. Docker, Podman, SSH and Fly are plugins behind the same interface, which means there is no code path in Husk that requires an account, a card or a network connection. Nothing is gated, nothing expires, and there is no edition of this product you are not already using.
There is no telemetry either. Not off by default — absent. No analytics, no crash reporter, no phone-home; the only network calls Husk makes are to the model provider you configured and to a registry when you pull an image. You can check that claim the same way we do, by reading the repository.
What a hosted tier would have to add before it was worth charging for.
what free means here
- no accountnothing to sign up for, nothing to log in to
- no keyOllama runs gemma, qwen or llama on your machine for nothing
- no Dockerthe local provider works without it, and says what you gave up
- no telemetryabsent from the codebase, not disabled by a flag
- Apache-2.0fork it, ship it, run it inside your company