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.
Read commit e07b253 on GitHub · chuyeowego/not-your-slave
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.
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.
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:
| Path | What lands there |
|---|---|
agent/agent.ts | Runtime config: model, reasoning effort, compaction, limits. |
agent/instructions.md | The always-on system prompt. Required. |
agent/tools/*.ts | Typed actions, running in your app process with your environment. |
agent/hooks/*.ts | Observers of the runtime event stream. Cannot change the model's context. |
agent/channels/*.ts | HTTP surfaces: the built-in API, and any route you write yourself. |
agent/schedules/*.ts | Cron 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.
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.
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:
| Model | Per wake-up | Per 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.
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.
// 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.
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.
// 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.
// 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.
package.json pins eve, ai, zod, and the OpenRouter provider, and wires the scripts: dev, build, start, typecheck.tsconfig.json is the scaffold's, with noEmit - eve does its own bundling, so tsc is purely a checker here..gitignore covers .env*, .data/, .eve/ and build output. The API key never enters the repo, and neither does the agent's memory, which becomes a real concern in commit 6.AGENTS.md and CLAUDE.md are the scaffold's notes for coding agents working in the project.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.
Why does pointing model at OpenRouter force you to add modelContextWindowTokens?
What makes model price a design constraint here rather than a preference?
The scaffolded channels/eve.ts lists vercelOidc(), localDev(), and placeholderAuth(). What follows for the browser UI built later in this series?
In eve, how does a new tool become visible to the model?
Why treat instructions.md as engineering rather than copywriting in this project?
Next: commit 2 gives it a memory - one append-only file, and the two tools that read and write it.