Skip to main content

Dock the Run-Chrome Yourself

The Task Check List and Subagent List are run-level chrome — they describe the state of the whole turn, not of any one message. So the SDK docks them in a fixed strip that sits outside the thread's scroll box, between it and the composer.

Dock the Run-Chrome Yourself

By default the SDK docks the Task Check List and Subagent panels in a fixed strip between the thread and the composer. Set hideRunChrome to true and the built-in panels are no longer rendered — you place the exported <TaskList> / <SubagentList> wherever you like, e.g. a right rail or a separate tab.

import {
  Chatbot, TaskList, SubagentList, AsgardThemeScope,
} from '@asgard-js/react';
import { deriveTasks, deriveSubagents } from '@asgard-js/core';

// conversation comes from useAsgardContext()
const tasks = deriveTasks(conversation);
const subagents = deriveSubagents(conversation);

<Chatbot hideRunChrome {...rest} />

<AsgardThemeScope theme={myTheme}>
  <SubagentList subagents={subagents} locale="en-US" />
  <TaskList tasks={tasks} locale="en-US" />
</AsgardThemeScope>

Outside the <Chatbot> subtree the design tokens the chat shell emits are gone, so wrap them in <AsgardThemeScope> — otherwise they fall back to the light-themed defaults.

Loading chatbot...

Why Outside the Scroll Box

They used to render at the tail of the thread and scroll with the messages. The result: during a streaming run the thread kept growing and auto-scrolling, shoving the panels around. Moved into a fixed strip, they now stay put while a run streams.

The strip has two more rules:

  • Capped at 50% of the body height, scrolling internally past that — the thread always keeps the other half
  • When there is no chrome, the strip does not exist and takes no space — the composer sits flush against the last message

Placing Them Yourself

<Chatbot hideRunChrome {...rest} />

With that set, the built-in panels are not rendered and you can put the exported <TaskList> / <SubagentList> anywhere — a right rail, a drawer, a separate tab.

import { TaskList, SubagentList, AsgardThemeScope } from "@asgard-js/react";
import { deriveTasks, deriveSubagents } from "@asgard-js/core";

// conversation comes from useAsgardContext()
const tasks = deriveTasks(conversation);
const subagents = deriveSubagents(conversation);

<AsgardThemeScope theme={theme}>
<SubagentList subagents={subagents} locale="en-US" />
<TaskList tasks={tasks} locale="en-US" />
</AsgardThemeScope>
Always wrap in AsgardThemeScope

Outside <Chatbot> the panels miss the design tokens the chat shell emits and fall back to the light-themed defaults. See Re-establish the Theme.

Where the Data Comes From

Both panels take plain arrays — they are not coupled to SDK state:

function TaskList(props: { tasks: Task[]; locale?: Locale }): ReactNode;
function SubagentList(props: { subagents: Subagent[]; locale?: Locale }): ReactNode;

deriveTasks / deriveSubagents fold both out of a Conversation. To subscribe rather than recompute, core also offers createDerivedStores, with tasksEqual / subagentsEqual for comparison.

import {
deriveTasks, deriveSubagents,
tasksEqual, subagentsEqual,
createDerivedStores,
} from "@asgard-js/core";

Types

interface Task {
id: string;
subject: string; // Noun-form title; shown when not in progress
activeForm?: string; // Present-continuous label; preferred while in progress
description?: string; // Full detail, shown when the row is expanded
status: "pending" | "in_progress" | "completed";
}

interface Subagent {
parentToolUseId: string; // Correlation key = the Agent tool-call that spawned it
agentId?: string;
subagentType?: string; // e.g. general-purpose
description?: string;
status: "running" | "completed" | "failed" | "cancelled";
summary?: string;
tools: SubagentToolCall[]; // The child tool-calls this subagent ran
}

A subagent's terminal status comes only from the subagent.complete event — it is never inferred from the Agent tool-call's own completion (async subagents complete their tool-call early with async_launched).

See Also