Skip to content

Guided tour

FabrCore 2.0 · Release and package availability

These guides track the current 2.0 source. Stable 2.0.0 publication is pending; package commands show the release target. Until it is published, follow the source quick start or use a matching available prerelease set. Release migration · Runtime modes

MODULE 06 · LESSON 6.1

Upgrade the assistant to a harness

Upgrade the assistant from one turn to tracked multi-step work.

Lesson 33 of 86 · FabrCore 2.0

Overview

A plain ChatClientAgent finishes when its model stops. A harness adds todos, modes, completion loops and delegation so unfinished work can be observed and continued. CreateFabrCoreHarnessAgent also supplies FabrCore history, tracked model calls and snapshot integration.

What the harness adds to an AI call

A normal AI run may stop after one answer even when a larger investigation has unfinished steps. The harness keeps explicit todos, operating mode and delegation state around that reasoning call. Its completion policy can decide whether another iteration is needed. Those capabilities make remaining work inspectable; they do not make a model's claim of success authoritative.

Why the wrapper's RunAsync matters

CreateFabrCoreHarnessAgent supplies the model component together with FabrCore integration for tracked calls, history and session snapshots. Call the returned wrapper's RunAsync from OnMessage. Calling only the inner Agent bypasses the wrapper's completion/persistence behavior, producing an agent that can appear to have a harness while losing the lifecycle guarantees the tour is trying to demonstrate.

Run through the harness wrapper

  1. Use the complete harness agent example and select its alias for the assistant. Resolve the same configured business tools.
  2. Create the harness in OnInitialize and call FabrCoreHarnessResult.RunAsync from OnMessage. Do not call the inner Agent.RunAsync directly.
  3. Read remaining todos and include unfinished work in the response. Use AsFabrCoreHarnessAgent only outside a proxy when you will supply persistence and other host services yourself.
ResearcherAgent.cs · complete class, required namespaces included
using System.ComponentModel;
using FabrCore.Core;
using FabrCore.Sdk;

[AgentAlias("researcher")]
[Description("Researches a question end to end and reports back.")]
[FabrCoreCapabilities("Breaks a research goal into tracked steps, delegates lookups to specialist agents, and reports a consolidated answer.")]
public class ResearcherAgent : FabrCoreAgentProxy
{
    private FabrCoreHarnessResult harness = null!;

    public ResearcherAgent(AgentConfiguration config, IServiceProvider serviceProvider, IFabrCoreAgentHost fabrcoreAgentHost)
        : base(config, serviceProvider, fabrcoreAgentHost) { }

    public override async Task OnInitialize()
    {
        var tools = await ResolveConfiguredToolsAsync();
        harness = await CreateFabrCoreHarnessAgent(config.Models ?? "default", "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;
    }
}
Reporting Honestly · reference snippet
public override async Task<AgentMessage> OnMessage(AgentMessage message)
{
    SetStatusMessage("Planning...");

    var run = await harness.RunAsync(message);
    var text = run.Text;

    // Delegations stranded by a restart — see references/durability.md.
    if (harness.DescribeLostDelegations() is { } lost)
    {
        text += $"{Environment.NewLine}{Environment.NewLine}{lost}";
    }

    var remaining = await harness.GetRemainingTodosAsync();
    if (remaining.Count > 0)
    {
        text += $"{Environment.NewLine}{Environment.NewLine}Not completed within the iteration budget:{Environment.NewLine}"
            + string.Join(Environment.NewLine, remaining.Select(item => $"- {item.Title}"));
    }

    SetStatusMessage(string.Empty);

    var response = message.Response();
    response.Message = text;
    return response;
}

Compare a completed and unfinished investigation

  1. Create a disposable instance of the illustrated harness agent and ask for a two-step fixture investigation: look up SR-1042, then summarize the returned facts.
  2. Inspect its todos and tool results. A finished response should correspond to completed work, not merely a sentence saying “Done.”
  3. Make one dependency unavailable and repeat. Expect an unfinished/blocked item and an explanation of what remains, rather than a success message that omits the failed step.

The meaningful result is agreement between the final answer and recorded work state. The harness makes that state available; your application must present it honestly.

If the result is different

Calling the inner agent bypasses the wrapper's snapshot behavior. A successful model response is not proof every todo is complete.

Go deeper

Explore the related documentation.