Agent Harness
Build agents that finish multi-step work
A normal chat agent owns one turn: the model calls tools until it decides to answer. The FabrCore Agent Harness adds a typed work list, an outer iteration loop, optional planning mode, background delegation, and durable session snapshots. Use it when an agent must keep working until a bounded plan is complete.
Use CreateChatClientAgent for ordinary one-turn chat. Use CreateFabrCoreHarnessAgent when one agent owns a durable plan across turns. Use a Surface task squad when a blueprint-defined coordinator should complete one multi-agent run inside a single turn.
Minimal Harness agent
CreateFabrCoreHarnessAgent is the agent-side entry point. It resolves the tracked chat client, Orleans-backed history, context compaction, configured tools, and the persisted Harness session.
using FabrCore.Core;
using FabrCore.Sdk;
[AgentAlias("researcher")]
[Description("Researches a goal end to end.")]
public sealed class ResearcherAgent : FabrCoreAgentProxy
{
private FabrCoreHarnessResult harness = null!;
public ResearcherAgent(
AgentConfiguration config,
IServiceProvider services,
IFabrCoreAgentHost host) : base(config, services, host) { }
public override async Task OnInitialize()
{
var tools = await ResolveConfiguredToolsAsync();
harness = await CreateFabrCoreHarnessAgent(
config.Models ?? "default",
threadId: "main",
tools);
}
public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
var run = await harness.RunAsync(message);
var response = message.Response();
response.Message = run.Text;
return response;
}
}
FabrCoreHarnessResult.RunAsync.
Calling the inner Agent.RunAsync bypasses the snapshot wrapper and silently disables durable Harness state.
What the Harness composes
| Capability | What it gives the model | Why it matters |
|---|---|---|
| Todos | todos_add, todos_complete, todos_get_remaining, and related tools | The plan becomes typed state that the host can inspect. |
| Modes | mode_get and mode_set | Planning can stop for review before execution begins. |
| Loop | Invisible host behavior | The agent is reinvoked when bounded work is still outstanding. |
| Background agents | background_agents_* tools | The model can fan work out and collect monitored, ACL-governed results. |
| Compaction | No extra tool | Every long tool loop uses the context compaction ladder. |
Plan and execute modes
Modes are enabled by default. Pass the full AgentMessage to RunAsync so the Harness can read message.Args["_plan-mode"].
| Value | Starting mode |
|---|---|
Missing, invalid, or "true" | plan by default |
"false" | execute |
MissingPlanModeBehavior can preserve the current mode or choose execution when the flag is omitted. An explicit valid value always wins.
Bounded completion loops
HarnessLoopMode is a flags enum. Evaluators run in flag order; the first evaluator that finds unfinished work continues the run.
| Mode | Continues while | Cost |
|---|---|---|
Todo | Incomplete todos remain in execution mode | No extra judge call |
Background | Delegations are still running | Delegate execution |
Marker | A configured completion marker is absent | No extra judge call |
Judge | A judge model decides the request is not complete | One extra LLM call per evaluation |
The default iteration cap is 10. It is a budget, not a promise of completion. Read GetRemainingTodosAsync() and tell the caller what did not finish.
Background delegation
External delegates are FabrCore agents with real handles. They are health-probed, represented with registry descriptions and capabilities, called through FabrCore messaging, and governed by normal ACL rules.
{
"args": {
"_HarnessBackgroundAgents": "eric:crm,eric:policy-desk",
"_HarnessBackgroundTimeoutSeconds": "180",
"_HarnessLoop": "todo,background",
"_HarnessLoopMaxIterations": "8"
}
}
Private in-process specialists are a second topology. Create them with CreateInternalAgentAsync and add InternalAgentResult.AsBackgroundAgent(). They receive isolated tracked model clients, timeout and concurrency bounds, child attribution, and fail-closed tool scopes. Only explicitly classified read/compute tools are allowed in concurrent background policies.
Durability model
- Todos, mode, delegation records, and loop position are stored under
_harness_session:{threadId}. - Conversation history remains in Orleans
MessageThreads; it is not duplicated into the Harness snapshot. - Snapshots are restored during activation and written after every wrapped run, including exceptional exits.
- In-flight background work cannot be resumed after deactivation. It is marked
Lostand must be reported or deliberately retried. - Set
_HarnessSessionPersistence=falseonly for high-frequency agents that do not need cross-turn Harness state.
Blueprint configuration
| Argument | Purpose |
|---|---|
_HarnessMode | Enable plan/execute modes; defaults to true. |
_HarnessLoop | Comma-separated loop modes such as todo,background. |
_HarnessLoopMaxIterations | Maximum outer-loop reinvocations. |
_HarnessBackgroundAgents | Comma-separated qualified delegate handles. |
_HarnessBackgroundTimeoutSeconds | Timeout for each delegated request. |
_HarnessSkills | Exact versioned Harness Skill references. |
Production checklist
- Set
ContextWindowTokensandMaxOutputTokensso long runs have an in-call bound. - Keep a finite iteration cap and delegation timeout.
- Describe delegates for the orchestrating model, including what they should not do.
- Report remaining todos and lost delegations honestly.
- Use durable Orleans storage when Harness state must survive process restarts.
- Remember that upstream Harness APIs remain experimental.