Harness.create(); you construct each layer and hand it the ones below. This is what the
assembly looks like, and then what each line is for.
Construct one of each per process. The store holds open database handles; the provider holds an
engine connection and a reap timer; two of either is two of everything.
Four contracts nothing else tells you
These were found by writing the CLI — the first consumer to assemble the layers from outside a test. Every one is a compile error in TypeScript and a confusing runtime failure without it.Events takes the store. It has no useful default. Miss it and the failure is
Cannot read properties of undefined (reading 'appendEvents') from inside an emit, several
layers below your code.
Actor is a string. Not an object. Pass { kind: "user", id } to a store method and
better-sqlite3 reads it as a named-parameter binding, and fails with an arity error that
names nothing.
A commit is credited to a FileCommitAuthor.
{ by: { kind: "actor", id: "user-42" } } — or execution, or import. Not a name.
A workspace whose permissions do not name the actor cannot run commands in it. That is
correct — it is how one user’s agent is kept out of another user’s workspace — but it means
creating a workspace is not enough. Grant the actor at creation:
Mapping to your users
The harness has no users table and wants none. An actor is whatever string identifies a principal in your system — a user id, a service account, a tenant. You decide the mapping:- one workspace per user, per project, or per conversation
- which actors may work in each, with what scope and what mode
- which connector credentials each actor holds
Lifecycle
reapIntervalMs: 0 to the provider so a timer never fires mid-command, and call
sandbox.reap() by hand if it wants to.
Without a model
core has no LLM in it. If your application drives its own loop, or just wants sandboxed,
versioned, auditable command execution, stop at BashTool: