---
product: "AG Studio"
title: "Custom Harness"
description: "Learn how to render the conversation in your own UI, or replace the harness entirely with machinery of your own."
framework: angular
version: "3.0.0"
related:
    - title: "Harness Overview"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-harness/"
    - title: "Built-in Harness"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-builtin-harness/"
    - title: "Direct LLM Runner"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-direct-llm-runner/"
    - title: "Client Tool Runner"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-client-tool-runner/"
    - title: "Custom Runner"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-custom-runner/"
llms: "https://www.ag-grid.com/studio/archive/3.0.0/llms.txt"
---

# Custom Harness

A custom harness applies when the conversation, or the interface, is application code. There are three depths, and replacing the harness is the deepest of them:

| You replace | You keep | Use |
| --- | --- | --- |
| The loop | Threads, persistence, chat UI, tools | [Custom Runner](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-custom-runner/), or [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-client-tool-runner/) if Studio should run its tools |
| The chat UI | Threads, persistence, agents, tools | [Your own UI](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-custom-harness/#your-own-ui), below |
| The conversation itself | Tools and context | [Implementing a harness](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-custom-harness/#implementing-a-harness), below |

## Your Own UI

You can keep Studio's harness - agents, threads, persistence, tool execution - and render the conversation yourself. `createAiHarness` returns the harness, so hold on to it:

```ts
let harness: AgAiHarness;

const studioProperties = {
    ai: ({ api }) => {
        harness = createAiHarness(api, { adapter });
        return harness;
    },
    initialState: {
        ...myState,
        // Hide the built-in panel; your UI is the chat surface.
        panels: { ai: { collapsed: true } },
    },
};
```

Once Studio is running, drive a session from your own components:

```ts
const session = await harness.createThread({ agentId: 'lead' });

session.addEventListener('changed', () => render(session.messages, session.status));
session.sendMessage('Add a chart of sales by region');
```

Session snapshots are immutable and change reference when they change, so they drive a React or Vue render loop directly. See [Sessions](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-builtin-harness/#sessions) for the reader contract, and [Chat UI](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-chat-ui/) for what the built-in panel does with them.

## Implementing a Harness

You can also implement the harness interface yourself and hand it to `ai`. Studio's panel will still render it, because the panel only ever reads the session surface.

```ts
const studioProperties = {
    ai: myHarness,
};
```

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `addEventListener` | `Function` |  | Register a listener for harness-level changes (roster or thread list). The event carries no payload: re-read the snapshots when it fires. Remove it with AgAiHarness.removeEventListener. |
| `removeEventListener` | `Function` |  | Stop notifying a listener added with AgAiHarness.addEventListener. |
| `agents` | `readonly AgAiAgentDescriptor[]` |  | Agents that can speak. New reference on change. |
| `models` | `readonly AgAiModel[]` |  | The models a reader may choose between, in the order they are offered. New reference on change. Absent or empty means this harness offers no choice, and the chat panel shows no model picker. |
| `threads` | `readonly AgAiThreadSummary[]` |  | Conversation catalogue. New reference on change. Empty is a valid state: Studio's own harness creates a conversation when the reader sends their first message, so a dashboard nobody has spoken to has no threads at all. |
| `promptStarters` | `readonly AgAiPromptStarter[]` |  | Suggestions to offer in a conversation nobody has said anything in yet, shown above the message box until one is chosen or a message is typed. Absent or empty shows nothing. |
| `getAgent` | `Function` |  | One agent from the roster, or `undefined` when nothing holds that id. |
| `getThread` | `Function` |  | One conversation's summary, or `undefined` when nothing holds that id. |
| `openThread` | `Function` |  | Idempotent: the same `threadId` returns the same live session. |
| `createThread` | `Function` |  | Start a conversation with the named agent and return its live session. Rejects when no agent holds that id. |
| `deleteThread` | `Function` |  | Remove a conversation, closing its session and taking any conversation nested below it with it. Does nothing when nothing holds that id. |
| `getSession` | `Function` |  | The live session for a thread if it is already open, without opening or hydrating one. Returns `undefined` for a thread not yet opened, or when the harness surfaces no such session (e.g. a delegate sub-run it does not track). Lets the UI bind to a sub-run mid-flight. |
| `setThreadModel` | `Function` |  | Set the model a conversation uses from now on, as AgAiThreadSummary.model. A harness that offers models but does not implement this keeps no record of the choice, and the chat panel remembers it only for as long as it stays open. |
| `dispose` | `Function` |  | Release any resources the harness holds (e.g. a persistence subscription). |

Persistence is then yours, because the `history` option applies only to the harness `createAiHarness` builds. So are the plan store and the delegation registry, which is why a harness of your own is handed no harness-owned tools.

If you are going this far, consider whether you need a harness at all. Tools and context work with nothing above them - see [Without a Harness](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-tools/#without-a-harness).

## Next

- [Client Tool Runner](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-client-tool-runner/) - the agent contract, events and rounds
- [Harness Overview](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-harness/) - what you are replacing
- [Tools Overview](https://www.ag-grid.com/studio/archive/3.0.0/angular/ai-tools/) - the tools your harness still drives
