The layers
harness — Compose a Product
Compile declarative artifacts — agent definitions, skills, instructions — into a configured durable agent
- Building an agent product with skills and instruction documents
- Turning static definitions into system prompts and middleware
- Understanding why New returns a factory, not an agent
You can compose an agent product from definitions, skills, and instructions with
pkg/harness.
Resolvers supply the artifacts. The harness compiles them into a system prompt,
first-run seed entries, a skill tool, and middleware. Then it returns a
configured durable.Agent and gets out of the way.
proj := os.DirFS(workdir)
h, err := harness.New( harness.WithCatalog(cat), harness.WithDefaultModel("anthropic-messages/claude-sonnet-4-6"), harness.WithWorkDir(workdir), harness.WithSkills( def.Skills(def.Skill{Name: "summarize" /* ... */}), // declared in code fs.Skills(proj, ".agents/skills"), // the project wins ), harness.WithInstructions(fs.Instructions(proj, "AGENTS.md")), // ... WithAgents, WithTools)
a, err := h.Agent(ctx, durable.WithSessionID("ticket-8472"))
msgs, err := a.Run(ctx, durable.Text("Commit the change in web/.")).Wait()New returns a factory
One harness serves many sessions and many working directories. It holds baseline configuration, and each build overlays the options of one conversation.
// Process-wide baseline: the skills of the user.h, err := harness.New( harness.WithSkills(fs.Skills(os.DirFS(userConfigDir()), "skills")), // ...)
// One session, in one repository, layered on top.a, err := h.Agent(ctx, durable.WithSessionID("ticket-8472"), harness.WithWorkDir(repo), harness.WithSkills(fs.Skills(os.DirFS(repo), ".agents/skills")),)A built agent keeps its snapshot for its lifetime. The next build reads the current state of every source.
The harness owns the prompt
h, err := harness.New( harness.WithPromptBuilder(myBuilder), // replace prompt construction harness.WithSeed(prompt.NoSeed), // start a fresh session empty)harness.New rejects agent.WithSystemPrompt, because prompt construction is the
job of this layer. On the first run of a fresh session, the harness prepends the
seed entries. A resumed session skips them, because history already holds them.
The seeder marks its own entries, and the harness passes the choice through.
prompt.DefaultSeed marks its environment block ai.InjectionMeta: the model
reads it, the transcript hides it, and the store keeps it, so it comes back on
resume. That is the usual choice, not an enforced one — a seeder that wants an
opening message the reader can see leaves the entry unmarked, and one that seeds
from live state marks it ephemeral. See
Messages.