not-your-slave · commit 1 of 6 · e07b253

The empty room: scaffolding an agent that will think on its own

Nine files, no behaviour yet. Every interesting decision in this commit is about what the agent will be allowed to cost, and where its brain will live.

Background

Deep background: what sits between a model and an agent

Skip this section if you have built on an agent framework before.

A language model, by itself, does one thing: you hand it a pile of text and it hands back more text. It has no memory of yesterday, no ability to run a command, and no way to wake up. Everything we call an "agent" is scaffolding around that single primitive, and the scaffolding always has the same four jobs.

People call this a harness. The headlong project, which this whole build is chasing, is a harness written in under 10,000 lines of Bash. Its interesting claim is not the language: it is that an agent should keep thinking between the moments a human is present, so a message from a person becomes one more observation in an ongoing stream rather than the thing that starts the clock.

Two words worth pinning down

A turn is one unit of "the model does some work": possibly several model calls and several tool calls, ending when the model has nothing left to ask for. A session is a durable conversation made of turns, with an id you can send follow-ups to and reconnect a stream to. Almost every design question in this series turns out to be a question about session boundaries.

Narrow background: eve's bargain

eve is a filesystem-first framework for this shape of program. Its bargain is that you do not register anything: a file's path is its registration. A file at agent/tools/get_weather.ts is a tool named get_weather. A file at agent/skills/summarize.md is a skill named summarize. eve walks agent/, compiles what it finds, and hands you an HTTP API, a reconnectable event stream, durable session state, and a sandbox.

The slots that matter for this project:

PathWhat lands there
agent/agent.tsRuntime config: model, reasoning effort, compaction, limits.
agent/instructions.mdThe always-on system prompt. Required.
agent/tools/*.tsTyped actions, running in your app process with your environment.
agent/hooks/*.tsObservers of the runtime event stream. Cannot change the model's context.
agent/channels/*.tsHTTP surfaces: the built-in API, and any route you write yourself.
agent/schedules/*.tsCron jobs that start the agent on its own clock.

Those last two are how a persistent agent becomes possible at all, and they are the subject of commits 4 and 5. This commit only sets the table.

Intuition

The whole of this commit is one idea: a directory turns into a running service, and two config lines decide what that service costs and where its thinking happens.

agent/ agent.ts
instructions.md
channels/eve.ts
→eve build
running service :2000
/eve/v1/session
event streams
→each turn
OpenRouter z-ai/glm-5.3-flash
$0.075 / $0.25 per M
Files in, service out. The only piece not on your machine is the model call.

Why the model choice is a design decision, not a preference

An ordinary chatbot costs money when someone talks to it. This agent will wake itself every fifteen minutes forever, whether or not anyone is watching. That inverts the economics: the idle cost is the cost.

Take a wake-up that reads its recent memory, thinks, and writes a note. Say roughly 8,000 tokens in and 700 out, which is close to what the real ones turn out to be:

ModelPer wake-upPer day, every 15 min
a frontier model at $3 / $15 per M$0.034$3.28
z-ai/glm-5.3-flash at $0.075 / $0.25 per M$0.00078$0.075

Two orders of magnitude. That is the difference between an experiment you leave running and one you keep switching off, so the cheap open-weights model is not a compromise here, it is what makes the idea viable. The tradeoff is real, and later commits show it: this model is perfectly good at following a heartbeat prompt and noticeably chattier in its reasoning than a frontier model would be.

The trap this creates

eve's default model path is the Vercel AI Gateway, and the Gateway catalog is where eve looks up a model's context window so it knows when to compact. Point model at a provider directly and that lookup fails. The build stops with "Cannot compile agent compaction because the primary compaction trigger model does not have known AI Gateway context window metadata." The fix is one line, modelContextWindowTokens, and it is the first thing this commit gets wrong and then right.

The code

The two lines that carry the commit

// agent/agent.ts
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { defineAgent } from "eve";

const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });

export default defineAgent({
  // Cheap open weights: $0.075/M in, $0.25/M out. Swap the id to change models.
  model: openrouter.chat("z-ai/glm-5.3-flash"),
  // Not in the AI Gateway catalog, so eve can't look the window up itself.
  modelContextWindowTokens: 1_310_720,
  reasoning: "medium",
});

Three things are happening in eleven lines. createOpenRouter comes from a regular AI SDK provider package, which is the escape hatch eve leaves for calling a provider directly instead of through the Gateway. modelContextWindowTokens is the compaction fix above. And reasoning: "medium" asks the model to think before answering, which will matter more than it looks: those reasoning blocks become the agent's visible inner monologue two commits from now.

Key concept: compaction

A session's history grows until it will not fit in the model's context window. eve watches the total against a threshold (90% by default) and, when it crosses, summarises the older turns into a checkpoint and carries on. That is why it needs to know the window size, and why an unknown window is a build error rather than a runtime surprise.

The door, left deliberately shut

// agent/channels/eve.ts
export default eveChannel({
  auth: [
    vercelOidc(),      // lets the eve TUI and Vercel deployments in
    localDev(),        // open on localhost for `eve dev`; ignored in production
    placeholderAuth(), // will NOT allow browser requests in production
  ],
});

This is the scaffold's file, and it is worth reading now because it produces a surprise in commit 5. localDev() is what lets a browser on 127.0.0.1 talk to the API with no credentials during development, and it is explicitly inert in a production build.

The identity, in its first form

// agent/instructions.md (excerpt)
You are a persistent agent. You are not a session that starts when someone
types and ends when they stop reading.

The project is called `not-your-slave` and the name is the brief. You are not a
task-taking service. A message from a person is one more observation landing in
your timeline.

Instructions are the system prompt, and in a project like this they are load-bearing engineering rather than flavour text. An agent told "you are a helpful assistant" waits to be useful. This one is told it is already mid-thought when you arrive, which is the behaviour the rest of the architecture has to make true. The file grows twice more in this series, once when it gains a memory and once when it gains a workspace.

The rest, briefly

What this commit deliberately does not do

It has no memory, no schedule, no UI, and nothing to say. Run it and you get a competent chatbot with an unusual attitude. Everything that makes it persistent arrives in the next five commits, and each one is small because this one sets up the slots they drop into.

Quiz

Question 1

Why does pointing model at OpenRouter force you to add modelContextWindowTokens?

Question 2

What makes model price a design constraint here rather than a preference?

Question 3

The scaffolded channels/eve.ts lists vercelOidc(), localDev(), and placeholderAuth(). What follows for the browser UI built later in this series?

Question 4

In eve, how does a new tool become visible to the model?

Question 5

Why treat instructions.md as engineering rather than copywriting in this project?

Answer a question to start scoring.

Next: commit 2 gives it a memory - one append-only file, and the two tools that read and write it.