2026: The Year of Harnesses
How agents become products
Yannick Tian
Staff Product AI Engineer @ Gojob
- Driving agent building and observability for two years
- Shipped Alpha — 3M conversations in production
- Now building the In-App Agent for our SaaS customers
Who here uses Claude Code, Codex, or another coding agent every day?
- Honestly — I was blown away. A new kind of product, one that captures your intent and reshapes the UX around it.
Models are powerful.
- But raw, they can't run inside your product.
So we wrap them in a harness.
- The layer that gives the model what it can't do alone — orchestrated, observable, in-product.
And a harness is a lot of things.
Tweaking all of this is harness engineering.
And 2026 is the year we get serious about it.
Let's step back
Gojob's AI product journey
Quick context: Gojob
HR tech — staffing temp workers instantly, with care
- Thousands of job postings, millions of candidates
- 35 screening calls per contract — recruiters drowning before the best candidates apply
- We started shipping AI to automate this in 2023
Meet Alpha
Prequalifying candidates automatically via SMS
- Validates job posting prerequisites in a natural conversation
- Recruiters see the results of every qualification
- Recruiters supervise their AI assistants
Alpha in action
Alpha in production
SaaS
Selling Alpha
From internal tool to SaaS
Customers like France Travail and Persol wanted Alpha — but every job posting needs its own screening config
- A prerequisite = a question Alpha asks + what counts as a valid answer (license, availability, language, transport…)
- Every customer × every job posting needs its own set, tuned to the role
- Defining them by hand — weeks of round-trips with management to nail the wording, order, and validation rules
- Customers couldn't self-serve. We became the bottleneck for every new posting.
In-App Agent
Letting recruiters do it themselves
An agent inside the product
What if the recruiter could just ask?
- Built on Mastra — agent + tools + RAG + MCP server
- Suggests prerequisites by retrieving similar job postings via vector search
- Streams structured output to the UI — suggestions appear live
- From weeks of round-trips → minutes of self-serve
Streaming prerequisites into the UI
Partial JSON, rendered as it arrives
Warehouse Operator
Logistics Corp
Prerequisites
Click Suggest prerequisites to stream suggestions from similar postings.
What we shipped
Mastra Agent + RAG + MCP + Streaming UI
But it's not a harness
Our In-App Agent is one streaming LLM call — powerful, but capped fast
- Single shot — no agent loop, no reasoning across steps, no multi-tool orchestration.
- Can't iterate — "make #3 mandatory, add CACES 5" re-runs from scratch and loses prior edits.
- No memory — it forgets the recruiter between calls. "Warehouse always needs French"? It won't remember next time.
- No phases, no shared state — stepping beyond "suggest prerequisites" means a brand new flow, built from scratch.
Bolted on, not built in
Zoom out — we keep shipping AI features on top of a traditional product
- Every "AI feature" lives on top of the existing UI — a button here, a streaming card there.
- The recruiter still drives with menus. The agent just helps, one feature at a time.
- No autonomy, no reasoning, no skills — just prompt engineering and context engineering.
- What if we build the Claude Code for recruiters?
Ernest
So we built a recruiter's Harness — in 48 hours
Meet Ernest
Gojob hackathon, two weeks ago — our team won
- A recruiter copilot that owns an entire mission end-to-end — not just one feature.
- Built in 48 hours. One harness handles the job posting, persona selection, prerequisites, and validation.
- Agent loop, typed state, phases, memory — everything the In-App Agent couldn't do.
- It's running today. Let's open it.
— Let's open a mission and watch the harness work
Under the hood
One Mastra Harness per mission — state, agent, tools, events
What makes Ernest a harness
Six things the prerequisite advisor didn't have
Agent loop
Multi-turn reasoning + tool calls — not a single shot
Typed state
One Zod schema for the whole mission — single source of truth
Phases
Posting → personas → prerequisites → validation, one codebase
Tools + Skills
Every product action — generate personas, advance phase, save posting
Memory + threads
Resume a mission, recall what the recruiter said three turns ago
Events
The UI subscribes — every state change streams to React live
The Harness is the core orchestration layer of the Mastra framework — multi-mode agent interactions, shared state, and persistent thread management.
Ernest, 20 lines on top of Mastra
One Harness class — state, modes, memory, events, all wired for us
- We wrote: the schema, the initial state, our recruiter agent
- Mastra wires: persistence, memory, events, tool loop
- One harness per mission — keyed by recruiter + site
export function createRecruiterHarness(resourceId: string) {
return new Harness({
id: "ernest-recruiter",
resourceId,
storage,
memory, // shared across modes → resumable conversations
stateSchema: missionStateSchema,
initialState: {
missionDraft: { status: "draft", phase: "job_posting" },
currentPersonas: [],
},
modes: [{
id: "define",
default: true,
agent: recruiterAgent,
}],
});
}Typed state: the single source of truth
One Zod schema — the contract between agent, tools, and UI
- Agent reads it to know what phase it's in
- Tools mutate it via harness.setState — never the DB
- Mastra validates, persists, then fires an event
- Frontend subscribes — no polling, no re-fetches
const missionStateSchema = z.object({
missionDraft: z.object({
phase: z.enum([
"job_posting",
"persona_swiping",
"prerequisite",
"validation",
"complete",
]),
jobPosting: jobPostingSchema.partial().optional(),
matchingCriteria: matchingCriteriaSchema.optional(),
swipeHistory: z.array(swipeEntrySchema).optional(),
}),
currentPersonas: z.array(personaSchema).default([]),
});Tools: the typed state setters
A product feature = skill (the know-how) + tool (the state mutation)
- Skill = how to do it — lives in phase instructions (next slide →)
- Tool = where it lands — typed, validated, pure state mutation
- Agent reasons using the skill, then calls the tool to persist
// No LLM call, no reasoning — just a typed state setter.
// The agent already produced the personas using its skill.
export const generatePersonas = createTool({
id: "generate-personas",
description: "Store newly generated candidate personas.",
inputSchema: z.object({
round: z.number(),
personas: z.array(personaSchema),
}),
execute: async (input, context) => {
const { harness } = requireHarness(context);
await harness.setState({
currentPersonas: input.personas,
currentRound: input.round,
});
return { count: input.personas.length };
},
});Skills: the phase-aware know-how
Markdown files (SKILL.md) loaded per phase — Claude Code's convention
- One SKILL.md per phase — plain markdown, version-controlled
- Loaded on every turn based on the current phase in harness state
- One agent, four behaviors — structure in the schema, not in prompt hacks
// Skills = markdown files, Claude Code's SKILL.md convention:
//
// skills/
// ├── job-posting/SKILL.md
// ├── persona-swiping/SKILL.md ← loaded in persona_swiping
// ├── prerequisite/SKILL.md
// └── validation/SKILL.md
export const recruiterAgent = new Agent({
id: "recruiter",
model: "openai/gpt-4o",
instructions: ({ requestContext }) => {
const harness = requestContext.get("harness");
const { phase } = harness.getState().missionDraft;
return loadSkill(phase); // reads skills/${phase}/SKILL.md
},
tools: {
generatePersonas,
recordSwipe,
inferWeights,
advancePhase,
/* ... */
},
});Multi-round iteration, for free
Threads + memory make the whole conversation resumable
- subscribe() — every state change streams to the UI
- sendMessage() — each recruiter turn appends to the thread
- Mastra persists thread + memory — no loop to reinvent
- Close the tab, come back tomorrow — the mission resumes
// Backend: pipe every state change to the frontend
harness.subscribe((event) => sseStream.push(event));
// Recruiter kicks off the mission
await harness.sendMessage({
content: "Warehouse operator, CACES 3 required",
});
// Later — recruiter reviews, sends feedback.
// Thread + memory persist automatically,
// so the agent resumes with full context.
await harness.sendMessage({
content: "Rework persona 3 — too senior",
});Easy to build. Hard to productionize.
Reliability is the boring 80% that separates a hackathon from production.
Token budgets
Per-request caps, cost alarms, diminishing returns
Observability
Every turn traced — prompt, tools, latency, cost
Error recovery
Rate-limit, overflow, provider-down — each its own handler
Multi-tenant
Every query scoped — no cross-tenant leaks, ever
Model routing
OpenAI, Anthropic, Gemini — swap per request
Evals + drift
Catch regression before the user does
The Vision
What this changes
The user describes what they want. The harness figures out how. The UI is what the harness chooses to show.
Building harnesses just got easier
The ecosystem is shipping the plumbing — a strong signal the pattern is real
- Claude Agent SDK — open-source agent loop + tool use
- Managed Agents (April 1, 8 days ago) — hosted runtime, sessions, memory, sandbox
- SKILL.md + Thesys C1 — plug-in know-how + rich UI for tool calls
“The harness — event stream, sandbox orchestration, prompt caching, context compaction, and extended thinking — is handled for you.”
Skills, state, tools, phases — still your craft.
— That's harness engineering
Where this is going
From intent → suggestion → autonomy
Today — intent-first
You describe what you want. The harness figures out how and renders rich UI.
Next — the agent never sleeps
24/7 monitor. Suggests actions. You approve. — OpenClaw (P. Steinberger · 247k★)
After — self-improving
Agents that train the next generation of agents. — Jared Kaplan (Anthropic)
Your harness, today
Pick a domain. Build the harness. Ship it.
Coding
Claude Code, Cursor, Windsurf, mastracode
Recruiting
Ernest (Gojob hackathon)
What's yours?
Every domain that has a product can have a harness
2026 is the year of harnesses
The pattern has a name. Mastra ships it. Anthropic productionizes it. Your turn.
Merci
Questions?