Developer AI.xperience

2026: The Year of Harnesses

How agents become products

April 9, 2026·Marseille
Hosted by Qalico × Gojob
First of a long series — thank you to both teams for hosting
Yannick Tian

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
yannick.tian@gojob.com

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.
no stateno toolsno user memoryno streaming UIno multi-tenant safety

So we wrap them in a harness.

  • The layer that gives the model what it can't do alone — orchestrated, observable, in-product.
statetoolsflowmemoryUIreliabilitylifecycle
typed schema
multi-tenant filter
single source of truth
resume threads
session grants
input validation
permission checks
tool approval
MCP connections
subagent spawn
modes
phase-aware prompts
pause / resume
abort / steer
plan approval
thread persistence
observational memory
memory compaction
prompt caching
observation threshold
event subscribe
stream parsing
thinking indicators
optimistic UI
multi-modal input
token budgets
error classification
provider fallback
model routing
error recovery
rate limits
cost tracking
hook system
token counting
auth / OAuth

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.

State
typed schemamulti-tenant filtersingle source of truthresume threadssession grants
Tools = features
input validationpermission checkstool approvalMCP connectionssubagent spawn
Flow
modesphase-aware promptspause / resumeabort / steerplan approval
Memory
thread persistenceobservational memorymemory compactionprompt cachingobservation threshold
UI
event subscribestream parsingthinking indicatorsoptimistic UImulti-modal input
Reliability
token budgetserror classificationprovider fallbackmodel routingerror recovery
Lifecycle
rate limitscost trackinghook systemtoken countingauth / OAuth
Dive deeper →mastra-ai/workshops · harness-workshop

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

9:41
5G
A
Alpha
Gojob Recruiter Assistant
Message...

Alpha in production

0MConversations
0MSMS sent
0KPrevalidated workers
350Screening calls per hire

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

Job Posting

Warehouse Operator

Logistics Corp

Prerequisites

Click Suggest prerequisites to stream suggestions from similar postings.

What we shipped

Mastra Agent + RAG + MCP + Streaming UI

React FrontendStream APIMastra AgentRAG (pgvector)MCP ServerApplicative BackendDomainpartial JSONsimilar postingstoolsqueries / commands

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.
agent loopmemoryshared statephasestools

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.

ernest.gojob.com/missions

Let's open a mission and watch the harness work

Under the hood

One Mastra Harness per mission — state, agent, tools, events

ERNEST HARNESS (MASTRA)React FrontendRecruiter AgentToolsTyped State (Zod)Event StreamOpenAI GPT-4oPostgressendMessageinvokesetStateemitSSEmodel callpersist

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.
Mastra docsmastra.ai/reference/harness

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
ernest/agents/harness/factory.ts
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
ernest/agents/harness/state-schema.ts
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
ernest/agents/tools/generate-personas.ts
// 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
ernest/agents/recruiter.ts
// 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
ernest: recruiter interaction loop
// 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 death of menus

How we used to ship software vs. how we ship it now

Yesterday

Navigate to find features

  • Menus, forms, dropdowns
  • Users learn the UI
  • Click. Click. Click.
  • Features hidden in submenus
Today

Describe what you want

  • One input — captures intent
  • Agent picks the right tools
  • Rich UI rendered inline
  • "Saves clicks" is the entire premise

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
docs.claude.com · managed-agents-2026-04-01

The harness — event stream, sandbox orchestration, prompt caching, context compaction, and extended thinking — is handled for you.

Anthropic docs

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

Build yours · @yannicktian · gojob.com

2026 is the year of harnesses

The pattern has a name. Mastra ships it. Anthropic productionizes it. Your turn.

github.com/yannicktian/meetups · meetups-talk.vercel.app

Merci

Questions?

1 / 42