v0.1 · source

Skein is a terminal coding agent

It helps you write code: it reads the project, makes changes, runs builds and tests. What sets it apart is how its context works: the history lives in an event journal, and the tape the model sees is rebuilt from it on every turn.

View source Install

Skein is on npm (skein-swe) and Docker Hub. The fastest start is npx -p skein-swe skein.

Quick start

A short path, using RouterAI as an example — a Russian OpenAI-compatible provider.

  1. Sign up at routerai.ru and create an API key.
  2. Install Skein: npm install -g skein-swe (or run it without installing — npx -p skein-swe skein). Requires Node.js 22 or newer and bash; if a global install needs root, see Install.
  3. Save the key and check that it and the model respond:
    skein auth login routerai
    skein doctor
  4. Start the agent in your project directory:
    skein --dir ~/project

Instead of the interactive login, set ROUTERAI_API_KEY in the environment. auth login lists free and local options first and prints a key link for the selected provider.

Introduction

Skein is a terminal coding agent. “Terminal” here means the agent runs in a terminal: you type commands in the chat pane, watch what the agent does in the observation pane, and follow the work in the task pane. “Coding agent” means it helps write software: it reads the project, makes changes, runs builds and tests.

Skein is built differently from traditional coding agents. To see how, let us first look at how a coding agent usually works and where that design falls short.

How a coding agent usually works

An ordinary coding agent runs a ReAct loop: the model reasons, picks an action, the executor runs a tool and returns the result, and then the model reasons again. The model gets a system prompt and the user's task, and at each step chooses the next action: read a file, find a string, edit code, run a command. The result is appended to the history, and the loop repeats until the model says “done” or the budget runs out.

An agent like this usually has a small set of tools: read a file, find a file or a string by pattern, edit a file, create a file. The main one is the shell (shell or bash): it runs any command in the terminal — build the project, run tests, commit, launch a script. Often the shell is enough for almost everything, and the separate tools exist for convenience and safety on common actions. Skein adds two tools that other agents usually do not have — recall and search; they pull from the journal what has left the tape. More on them below.

The model's context is more than the history of actions. It holds the system prompt, the task, all the reasoning and tool output; for the model this is the only memory. There is no separate state — the goal lives in the text of this history. When the history no longer fits the model's window, it is truncated or compacted.

Model providers do not re-run the model on text it has already read: they keep its internal state and find it by the prompt prefix — that is the prompt cache. So input tokens that were already present in the previous call usually cost orders of magnitude less than new ones. A long history stops being expensive, and price and latency move to the background. Two other problems remain: attention and control.

Attention. The goal competes with kilobytes of old tool output. The model rediscovers what it already knows, returns to ground it has covered, and gradually loses the original task.

Control. A step has no clear boundary, and compaction rewrites the history. It is hard to say from the history what the model “knew” at the moment of a decision, and even harder to reproduce that later.

Why we compare with opencode

opencode is an open terminal coding agent that follows the same scheme. It is a fair comparison not because it is “bad”, but because it can be put under the same conditions: the same provider and model, the same reasoning mode, the same tasks and containers, and a headless run (opencode run, --continue). In our fix-ocaml-gc run it solved the task in 54 steps, and its context grew from about 3k to 300k characters. The cache made this cheap, but the history stayed a history.

How Skein is different

Skein is built around a different idea: history is data, not a prompt.

A note on the word “tape”. Below it means what the model sees on input: for an ordinary agent that is the growing history; for Skein it is the projection of the journal. It is the same visible model context, just built differently.

  • History is stored, not rewritten. Instead of a growing tape, Skein has a typed journal that is only appended to. Journal nodes are never rewritten, so the history stays complete and addressable.
  • The prompt is rebuilt. On every turn the model's context is rebuilt from the journal, deterministically: the same events produce the same tape. The tape is non-monotone: when the focus moves, closed states leave the screen but stay in the journal and remain available through recall and search.
  • Explicit states. A step of work is a state with a goal (what), a completion condition (Done when) and its own set of tools. Transitions between states are explicit. The user's request is a reference, not the active goal: the active goal belongs to the state.
  • The model proposes, the executor records. The model has no hidden memory; refusals and tool failures are the only facts the engine adds.
  • A session over separate tasks. The whole dialogue between the user and the agent (a session) consists of separate units of work, each with its own journal. Units are isolated, and only a short summary of each is visible outside. Continuity rests on reformulating the request and on those summaries.

recall and search are Skein's special tools. recall returns a specific turn or fragment of earlier work; search searches the journal. In an ordinary agent, the history either sits entirely in context or has already been compacted, and there is no way to pull an exact fact out of it.

What it buys: the goal stays salient at the decision point, every step has a clear boundary, and the journal can be reproduced and checked.

What it does not buy: it is not a cost optimization. Isolation can be more expensive because of re-reading the project. The condition is checked by the model's text, not by the engine, so a premature stop is possible.

One task end to end: fix-ocaml-gc

Let us walk one real task from the user's first message to the final answer. This is an illustrative example: it shows how Skein works and how its path differs from an ordinary agent such as opencode on the same task.

The user writes that they broke the OCaml garbage collector while trying to reduce free space in the major heap, and the compiler crashes during bootstrap. They ask to investigate and make sure the basic testsuite passes.

The engine records the request, probes the workspace, and asks the model to choose a starting state. The first turn looks like this, schematically:

[system]  base prompt: role and how to work
[human]   request
[system]  workspace facts: root, no VCS, network off, make/gcc/perl available
[human]   choose a starting state: Patch / Construction / Research

Patch

The model chooses Patch and formulates the subject of work: a broken run-length-compressed sweep in the major GC. The state's projection looks like this:

[system]  Start state Patch
[system]  workspace facts
[human]   Goal: find the defect in the existing artifact and make the needed fix
          Done when: the plan is done — the bug is found and fixed
          Plan: 1) find the file and the cause  2) fix it and stop with a report

Then the work happens inside the state. Schematically:

ActionWhat it gave
list /appproject structure
cat HACKING.adochow to build the compiler
find -newermtrecently changed files
grep in shared_heap.c suspicious pool traversal loops
edit p += Whsize_hd(hd) → p += wh

After 18 turns the model replies with text — the engine records Finish state Patch with the result: the cause is in pool_sweep, the free block size is stored in the header, and the step must advance by the size-class width.

Transition

The projection collapses. Only the base prompt, the request, Start state Patch, Finish state Patch and the question about the next state reach the tape:

[system]  Choose exactly one next state:
          Verification — the change is in place and the lines have been read back
          Research — this is a situation, not a defect to find and fix

At the peak of Patch the context was about 116k characters; at the transition, about 4.5k. This is not deletion: the journal is intact, and only what is needed for the next-step decision reaches the tape.

Verification

The model moves to the verification state.

[system]  Start state Verification
[human]   Goal: verify the changed artifact against acceptance
          Done when: acceptance passes, the result is derived from the material
          Plan: run the acceptance command and read the output;
                a zero exit code is not proof; do not change the artifact here
ActionWhat it gave
./configurebuild setup
make -j16 the compiler built, bootstrap passed
make -C testsuite one DIR=tests/basic 40 tests passed

The model replies with text — Finish state Verification.

End

[system]  Start state End
[system]  This is End. Read the tape — the request, the states you went through,
          what each did, which files changed, how it was verified —
          and write the final report.

Final answer: the defect is in pool_sweep (runtime/shared_heap.c), fixed with p += wh; the build passes and the basic testsuite reports 40 tests passed.

What matters here

  • The context is not a transcript. Each state has its own projection, and it is rebuilt from scratch.
  • The request stays a reference; the active goal belongs to the state.
  • The model's text is the boundary of a state (stop).
  • A closed state leaves the tape but stays in the journal.
  • The context still grows, but the cache makes it cheap. The point is not saving money but that the model sees the goal, not the whole history.

Mechanics

States and transitions

Each task moves through explicit states. A state is a stage of work with a goal (what exactly needs to be done), a completion condition (how to tell the stage is finished) and its own set of tools. A state has a short list of turns, and after each turn the model sees it again, not the whole task history.

There are five states:

StateWhat it doesWhere it can go
Research works out the situation and derives the needed result from the material End, Patch
Patch finds the defect in the existing artifact and fixes it Verification, Research
Construction builds a new artifact from a specification End, Research
Verification checks the change against the task's condition End (passes), Patch (fails)
End finishes the request with a final report —

The usual route is Research → Patch → Verification → End. If the check does not pass, the work returns to Patch and keeps fixing. The model chooses the transition, but the engine defines the allowed ones: from Patch you can go to Verification or Research, but not straight to End.

A state closes when the model replies with text. The engine records that reply as stop and moves the result into the journal. There is no separate check that the goal was reached: the condition is written in the state, but the engine does not enforce it. So a premature stop is possible — a known limitation.

Agents and units of work

The user's messages are handled by a separate agent — the session agent. It does not work with files directly: the workspace tools are not available to it. Instead, for each message it chooses one of six operations: respond, general, revision, task, plan, write_document.

An operation opens a unit of work — a separate task with its own event journal. Inside a unit the agent reads files, edits code and runs commands, but those steps do not leave it: the session agent sees only a short summary of the unit. So units do not interfere with each other, and the session stays coherent through the reformulation of the request and those summaries.

  • task — the state loop described above;
  • general — examines the workspace without changes;
  • revision — a change already described in the request (edits and commands);
  • plan — a plan of work without changes;
  • write_document — a large Markdown document, section by section;
  • respond — an answer from the dialogue and earlier work.

The tape

The tape is what the model sees on input. It is rebuilt from the journal on every turn, and the order of blocks is always the same:

  1. the base prompt — the agent's role and general rules;
  2. the user's request — as a reference, not as the active goal;
  3. Start state — which state the work started in;
  4. a brief note about the workspace;
  5. the state's turns — actions and observations;
  6. the working prompt — the goal, completion condition and plan of this state;
  7. Finish state — the result once the state is closed.

Only the journal determines the order and the content. The same events always produce the same tape — in that sense the build is deterministic. But it is non-monotone: when the focus moves to another state, the closed state leaves the tape. That is not a loss — the journal is intact, and recall and search can bring back what is needed.

Session and isolation

A session is the whole dialogue between the user and the agent. Each message becomes a separate unit of work: it has its own journal, its own set of states and its own summary. Units are isolated from each other: inside the next unit the step-by-step history of the previous one is not visible, only its short summary. So units do not inflate each other and do not drag along each other's tool output.

The price of isolation is re-reading the workspace. If a new task needs the same facts as the previous one, the agent has to read them again: they are not on the new unit's tape. Session continuity rests on two things: reformulating the request with the earlier messages in mind, and the short summaries of the units already completed.

Journal

The journal is the history of work as typed records: the request, turns, actions, observations, transitions between states, and stops. It is only appended to: nodes are never rewritten or deleted. So the journal can reconstruct what the agent did, in what order, and with what result.

The tape is built from the journal, not the other way around. If something left the tape when the focus moved, it stays in the journal. The recall and search tools pull a specific turn out of it or find what is needed in the journal — so the model can bring back a fact without re-reading everything.

Evaluation and limitations

We compared Skein with opencode on thirteen tasks. Each task was solved five times, with and without network, by both agents — 250 runs available for evaluation in total. 246 were solved (98.4%). By group: Skein with network — 63 of 65, Skein without network — 60 of 60, opencode with network — 65 of 65, opencode without network — 58 of 60. The whole series used one provider and one model — deepseek-v4-flash with low reasoning effort; this is not a claim of a state-of-the-art result.

Token measurements (solved runs only, 123 per agent):

Skeinopencode
input31.38M47.63M
from cache26.88M45.22M
new input4.50M2.41M
output1.24M0.94M
cache hit85.7%94.9%
turns24471800

The picture: opencode spends more tokens, but almost all of them are cache hits, so it is cheaper in money. Skein is smaller in total volume, but has more new input tokens and output: units do not share one growing prompt, so they re-read the workspace more often.

Hence the main conclusion: the difference is not about money. The cache makes a long tape cheap, so comparing by cost is pointless. The point is elsewhere — keeping the goal salient at the decision point and making a step reproducible. These properties are harder to measure, and we did not measure them automatically.

Limitations:

  • the engine does not check a state's completion condition, so a premature stop is possible;
  • isolation trades drift for repeated work and can cost more;
  • the whole series used one provider, one model and low reasoning effort;
  • the shell tool requires /bin/bash; on Windows, use WSL2, Git Bash/MSYS2 or Docker.

The protocol, all runs, tokens and per-state cost live in bench/results/campaign-k5/; the raw data is published with the release.

Install

Requires Node.js 22 or newer and bash.

npm install -g skein-swe
skein --version

If your npm global prefix is a system directory (for example /usr/local), a plain npm install -g fails with EACCES. Install with --prefix "$HOME/.local" and put ~/.local/bin on PATH: sudo is not needed. With nvm or volta the global prefix is already in your home directory. You can also run it without installing: npx -p skein-swe skein.

From source

git clone https://github.com/merfill/skein.git
cd skein
npm install
npm run build
npm pack
npm install -g --prefix "$HOME/.local" ./skein-swe-*.tgz
export PATH="$HOME/.local/bin:$PATH"
skein --version

Next, point Skein at a provider, a key and a model — interactively or with environment variables:

skein auth login
skein doctor

skein doctor checks that the key and the model respond. If everything is fine, the interface starts:

skein --dir ~/project

Windows

The agent needs /bin/bash, so on Windows the reliable path is WSL2 or Docker. Native PowerShell and cmd without bash will not work.

WSL2 (recommended):

  1. In an administrator PowerShell, run wsl --install -d Ubuntu and reboot.
  2. Inside Ubuntu, install Node.js 22:
    curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
    sudo apt-get install -y nodejs
  3. Keep projects in the WSL home directory (~/project), not under /mnt/c: it is faster and avoids permission problems.
  4. Then repeat the Linux install steps inside WSL.

Docker Desktop: run the Linux image from the block below — no Node needed inside.

Git Bash / MSYS2: may work only if /bin/bash is reachable from the launching shell, but in practice native Windows Node often cannot find that path. WSL2 or Docker is more reliable.

Docker

The published image is on Docker Hub:

docker pull mefrill/skein-swe:0.1.0
docker run --rm -it \
  -v "$PWD:/workspace" \
  -e ROUTERAI_API_KEY=... \
  -e SKEIN_MODEL=~deepseek/deepseek-v4-flash-latest \
  mefrill/skein-swe:0.1.0

The provider is set with environment variables; no model is bundled. The latest tag points to the newest version. On Windows PowerShell use ${PWD}, in cmd use %cd%; --env-file does not strip quotes from values.

Build from source: docker build -t skein:0.1.0 . in the repository root.