Page actions

React Deep Agents Subagents preview

Let an orchestrator delegate focused work while the user keeps one conversation. Specialist observations help explain progress, but the root agent still owns the final response.

The native React APIs on this page are experimental, private source-build packages. Run the repository example to use them; they are not an npm installation recipe.

Follow the dispatched task identity

The application associates a specialist with its actual dispatch and observes its messages and tool activity separately from the root. Keep the root result, child observations, and saved history distinct; similar names or adjacent events do not establish a parent relationship. The complete sources below show that attribution alongside the React presentation.

The React conversation walkthrough connects this capability to requests, tools, UI state, and saved history.

Start an aviation brief

Choose Run, type a message and choose Send or press Enter. Brief KASE and KDEN fills this exact draft without sending:

Brief me on KASE and KDEN: I need field data for both and the current weather at both.

Field data for KSFO fills this exact draft:

What is the field data for KSFO?

The roster names field-researcher and weather-analyst. All aviation and weather lookups use fixed sample data. The prompt requests one airport per task call and parallel dispatch when appropriate, but does not guarantee four calls, delegation in every answer, or only those two agent types. The graph also supports general-purpose dispatches and generic tools. The sidebar counts actual observed task calls.

Only an explicit Send creates the owned thread, lazily, using the native selector subagents. Opening Docs or Run, changing frontend or mode, navigating history, filling suggestions and choosing New conversation do not author a run. Mode navigation and browser history preserve the mounted frame and unsent draft. Reload and New clear local conversation state; New waits for the next Send to create a thread.

app.tsx
// eslint-disable-next-line @nx/enforce-module-boundaries -- The isolated consumer copies this internal example layout.
import { ExampleFooter, ExampleLayout } from '../../../../shared/react-example';
import { useCallback, useEffect, useState } from 'react';
import { useAgent } from '@threadplane/react';
import {
  ChatInput,
  ToolObservation,
  MessageList,
  Reasoning,
  Citations,
} from '@threadplane/react/chat';
import { Markdown } from '@threadplane/react/markdown';
import type { MessageRow } from '@threadplane/content/messages';
import {
  createConnectedApplication,
  type subagentsConnection,
} from './connection';
import type { ToolCard } from './projection';
 
/** Stable row renderer; the application owns Markdown snapshot lifetimes. */
export function renderMessage(
  row: MessageRow,
  cards: readonly ToolCard[] = []
) {
  if (row.role === 'tool') return null;
  if (
    row.role === 'assistant' &&
    !row.message.content &&
    !row.reasoning &&
    !row.message.citations?.length &&
    !cards.length &&
    row.message.toolCallIds?.length
  )
    return null;
  const label =
    row.role === 'user'
      ? 'You'
      : row.role === 'assistant'
      ? 'Assistant'
      : 'System';
  return (
    <article
      aria-label={label}
      className={`tp-chat-message tp-chat-message--${row.role}`}
    >
      <p className="tp-chat-message__role">{label}</p>
      {row.role === 'assistant' && row.reasoning && (
        <Reasoning snapshot={row.reasoning} />
      )}
      <Markdown snapshot={row.markdown} />
      {cards.map((card) => (
        <div className="tool-observation" data-call-id={card.id} key={card.id}>
          <h3>{card.subagentType ?? card.name}</h3>
          {card.kind === 'task' && card.fields === 'unavailable' && (
            <p>Task fields unavailable</p>
          )}
          {card.description !== undefined && <p>{card.description}</p>}
          <p>
            {card.resultStatus === 'error'
              ? 'Tool result: error'
              : `Observed delivery: ${card.status}`}
          </p>
          <ToolObservation
            name={card.name}
            argumentsText={card.argumentsText}
            resultText={card.resultText}
            label={
              card.resultText === undefined
                ? card.kind === 'task'
                  ? 'Observed specialist request'
                  : 'Observed tool request'
                : card.resultStatus === 'error'
                ? 'Observed tool error'
                : card.kind === 'task'
                ? 'Observed specialist result'
                : 'Observed tool result'
            }
          />
        </div>
      ))}
      {row.role === 'assistant' && row.message.citations && (
        <Citations citations={row.message.citations} />
      )}
    </article>
  );
}
export function SubagentsDemo({
  connection,
  onReady,
}: {
  readonly connection: ReturnType<typeof subagentsConnection>;
  readonly onReady: () => void;
}) {
  const [application] = useState(() => createConnectedApplication(connection));
  const [draft, setDraft] = useState('');
  const snapshot = useAgent(application);
  const renderRow = useCallback(
    (row: MessageRow) => renderMessage(row, snapshot.observations.get(row.id)),
    [snapshot.observations]
  );
  useEffect(() => {
    onReady();
  }, [onReady]);
  useEffect(
    () => () => {
      void application.dispose();
    },
    [application]
  );
  const status = snapshot.busy
    ? snapshot.activity === 'creating'
      ? 'Creating conversation…'
      : snapshot.activity === 'confirming'
      ? 'Confirming saved conversation…'
      : snapshot.activity === 'replacing'
      ? 'Releasing conversation…'
      : 'Receiving response…'
    : snapshot.outcome === 'success'
    ? 'Response saved.'
    : snapshot.outcome === 'aborted'
    ? 'Response stopped. Start a new conversation to continue.'
    : !snapshot.canSubmit
    ? 'Start a new conversation to continue.'
    : 'Ready.';
  return (
    <ExampleLayout className="subagents-demo">
      <header>
        <div>
          <h1>Deep Agents subagents</h1>
        </div>
        <button
          type="button"
          onClick={() => {
            setDraft('');
            void application.newConversation();
          }}
        >
          New conversation
        </button>
      </header>
      <p>
        Dispatch sample field research and weather tasks, and inspect separate
        native child streams. Aviation lookups use fixed sample data, including
        weather. Stop cancels local delivery; it does not roll back backend
        work.
      </p>
      <div className="seed-prompts" aria-label="Example prompts">
        {[
          {
            label: 'Brief KASE and KDEN',
            text: 'Brief me on KASE and KDEN: I need field data for both and the current weather at both.',
          },
          {
            label: 'Field data for KSFO',
            text: 'What is the field data for KSFO?',
          },
        ].map((prompt) => (
          <button
            key={prompt.label}
            type="button"
            disabled={!snapshot.canSubmit || snapshot.busy}
            onClick={() => {
              if (
                application.getSnapshot().canSubmit &&
                !application.getSnapshot().busy
              )
                setDraft(prompt.text);
            }}
          >
            {prompt.label}
          </button>
        ))}
      </div>
      <aside aria-label="Specialist roster">
        field-researcher · weather-analyst
      </aside>
      <p aria-label="Observed dispatch count">
        {snapshot.taskCount === null
          ? 'Dispatch observations incomplete'
          : `${snapshot.taskCount} observed task dispatches`}
      </p>
      <MessageList
        rows={snapshot.rows}
        renderMessage={renderRow}
        label="Conversation"
      />
      {snapshot.childObservations.length > 0 && (
        <details className="child-observations">
          <summary>Child response observations</summary>
          <p>
            Local observations from this open conversation. These are not
            restored execution history and are not linked to individual task
            cards.
          </p>
          {snapshot.childObservations.map((child, index) => (
            <section key={child.key} aria-label={`Observed namespace response`}>
              <h3>Observed stream {index + 1}</h3>
              <p className="namespace">{JSON.stringify(child.namespace)}</p>
              <p className="child-observation-status">{child.status}</p>
              <details open>
                <summary>Observed transcript</summary>
                <pre>{child.text}</pre>
              </details>
            </section>
          ))}
        </details>
      )}
      <ExampleFooter>
        {snapshot.error && <p role="alert">{snapshot.error}</p>}
        <ChatInput
          key={snapshot.viewGeneration}
          label="Message"
          value={draft}
          onValueChange={setDraft}
          placeholder="Ask about field data or weather…"
          hint="Enter to send. Shift+Enter adds a new line."
          busy={snapshot.busy}
          disabled={!snapshot.canSubmit && !snapshot.busy}
          onStop={
            snapshot.busy && snapshot.activity !== 'replacing'
              ? () => void application.stop()
              : undefined
          }
          onSubmit={(text) => {
            if (
              !application.getSnapshot().canSubmit ||
              application.getSnapshot().busy ||
              !text.trim()
            )
              return false;
            void application.submit(text);
            return true;
          }}
        />
        <p role="status">{status}</p>
      </ExampleFooter>
    </ExampleLayout>
  );
}
connection.ts
import { Client } from '@langchain/langgraph-sdk';
import { createSession } from '@threadplane/langgraph';
// eslint-disable-next-line @nx/enforce-module-boundaries -- Internal bridge is copied by the isolated build.
import type { RuntimeBridgeConfiguration } from '../../../../../libs/cockpit-runtime-bridge/src/index';
import {
  createSubagentsApplication,
  type SubagentsClient,
  type SubagentsSession,
} from './application';
import { copyData, record, text, LIMITS, type Checkpoint } from './authority';
export interface SubagentsConnection {
  readonly apiUrl: string;
  readonly headers: Record<string, string>;
}
export function subagentsConnection(
  configuration: RuntimeBridgeConfiguration
): SubagentsConnection {
  if (
    configuration.status === 'error' ||
    (configuration.status === 'configured' &&
      configuration.target.kind === 'ag-ui')
  )
    throw Error('Runtime configuration failed.');
  const target =
    configuration.status === 'configured' &&
    configuration.target.kind === 'langsmith'
      ? configuration.target
      : null;
  return {
    apiUrl: target?.apiUrl ?? new URL('/api', window.location.origin).href,
    headers: target ? { 'x-api-key': target.apiKey } : {},
  };
}
export function createSubagentsClient(
  input: SubagentsConnection
): SubagentsClient {
  const connection = Object.freeze({
      apiUrl: input.apiUrl,
      headers: Object.freeze({ ...input.headers }),
    }),
    known = new Set<string>();
  const client = new Client({
    apiUrl: connection.apiUrl,
    apiKey: null,
    defaultHeaders: connection.headers,
    callerOptions: { maxRetries: 0 },
  });
  function owned(id: string, signal: AbortSignal) {
    if (!known.has(id) || signal.aborted)
      throw Error('Conversation unavailable.');
  }
  return {
    async createThread(signal) {
      if (signal.aborted) throw Error('Aborted');
      const id = crypto.randomUUID(),
        created = await client.threads.create({
          threadId: id,
          ifExists: 'raise',
          signal,
        });
      if (signal.aborted || created.thread_id !== id)
        throw Error('Conversation creation was not confirmed.');
      known.add(id);
      return id;
    },
    sessionFactory(id) {
      if (!known.has(id)) throw Error('Unknown conversation.');
      const session = createSession({
        assistantId: 'subagents',
        threadId: id,
        apiUrl: connection.apiUrl,
        clientOptions: { maxRetries: 0, defaultHeaders: connection.headers },
      });
      try {
        const state = session.getSnapshot();
        if (
          ![
            'getSnapshot',
            'subscribe',
            'submit',
            'load',
            'stop',
            'dispose',
          ].every(
            (k) => typeof session[k as keyof typeof session] === 'function'
          ) ||
          ![
            state.messages,
            state.toolCalls,
            state.interrupts,
            state.subgraphs,
          ].every((x) => Array.isArray(x) && x.length === 0)
        )
          throw Error('Rejected session');
        return session as SubagentsSession;
      } catch {
        void Promise.resolve(session.dispose()).catch(() => undefined);
        throw Error('Runtime session is unavailable.');
      }
    },
    async readCurrent(id, signal) {
      owned(id, signal);
      const value = await client.threads.getState(id, undefined, { signal });
      owned(id, signal);
      return value;
    },
    async readHistory(id, signal) {
      owned(id, signal);
      const value = await client.threads.getHistory(id, { limit: 1, signal });
      owned(id, signal);
      return value;
    },
    async readCheckpoint(requested, signal) {
      const v = copyData(requested, false);
      if (
        !record(v) ||
        !text(v.thread_id) ||
        v.checkpoint_ns !== '' ||
        !text(v.checkpoint_id) ||
        v.checkpoint_id.length > LIMITS.fieldText ||
        Object.keys(v).some(
          (k) =>
            ![
              'thread_id',
              'checkpoint_ns',
              'checkpoint_id',
              'checkpoint_map',
            ].includes(k)
        ) ||
        (v.checkpoint_map !== undefined &&
          (!record(v.checkpoint_map) ||
            Object.keys(v.checkpoint_map).length !== 1 ||
            v.checkpoint_map[''] !== v.checkpoint_id))
      )
        throw Error('Checkpoint unavailable.');
      const c = v as unknown as Checkpoint;
      owned(c.thread_id, signal);
      const value = await client.threads.getState(
        c.thread_id,
        c as Parameters<typeof client.threads.getState>[1],
        { signal }
      );
      owned(c.thread_id, signal);
      return value;
    },
  };
}
export function createConnectedApplication(connection: SubagentsConnection) {
  return createSubagentsApplication(createSubagentsClient(connection));
}

Read parent task cards

Parent cards preserve canonical tool-call IDs and declaration order. They show the actual literal description, subagent_type, arguments and results. Generic tools and general-purpose task calls remain literal observations too. Missing or unsupported task fields stay visibly unavailable; the preview does not invent a specialist name, result or dispatch count.

projection.ts
import {
  ConversationBudget,
  copyData,
  displayUnits,
  LIMITS,
  literal,
  record,
  text,
  validMessageMetadata,
} from './authority';
 
export interface ToolCard {
  readonly id: string;
  readonly name: string;
  readonly kind: 'task' | 'tool';
  readonly description?: string;
  readonly subagentType?: string;
  readonly fields: 'available' | 'unavailable';
  readonly argumentsText: string;
  readonly resultText?: string;
  readonly resultStatus?: 'success' | 'error';
  /** Native complete describes resolved delivery, not raw execution success. */
  readonly status: string;
}
export interface ParentProjection {
  readonly status: 'available' | 'incomplete';
  readonly reason?: string;
  readonly taskCount: number;
  readonly cards: ReadonlyMap<string, readonly ToolCard[]>;
}
 
/** Literal parent observations ordered by the one declaring assistant's call IDs.
 * Use one conversation ledger and one stable parent scope across followups. */
export function projectParentObservations(
  observedMessages: unknown,
  observedTools: unknown,
  budget: ConversationBudget,
  scope = 'parents',
  rawStatuses?: unknown
): ParentProjection {
  const cards = new Map<string, readonly ToolCard[]>();
  let taskCount = 0;
  try {
    const data = copyData({
      messages: observedMessages,
      tools: observedTools,
      rawStatuses,
    }) as { messages: unknown; tools: unknown; rawStatuses: unknown };
    if (
      !Array.isArray(data.messages) ||
      !Array.isArray(data.tools) ||
      (data.rawStatuses !== undefined && !record(data.rawStatuses))
    )
      throw new Error('Unavailable parent data');
    const messages = data.messages,
      tools = data.tools,
      ids = new Set<string>(),
      catalog = new Map<string, Record<string, unknown>>(),
      owners = new Map<string, { id: string; index: number }>(),
      results = new Map<
        string,
        { message: Record<string, unknown>; index: number }
      >();
    let units = 0;
    for (const [index, m] of messages.entries()) {
      if (
        !record(m) ||
        !text(m.id) ||
        ids.has(m.id) ||
        typeof m.content !== 'string' ||
        !validMessageMetadata(m) ||
        !['user', 'assistant', 'tool', 'system'].includes(m.role as string)
      )
        throw new Error('Invalid parent message identity');
      ids.add(m.id);
      if (
        !record(m.delivery) ||
        !text(m.delivery.generation) ||
        !['streaming', 'complete'].includes(m.delivery.phase as string) ||
        (m.delivery.phase === 'streaming' &&
          m.delivery.outcome !== undefined) ||
        (m.delivery.phase === 'complete' &&
          !['success', 'error', 'aborted', 'interrupted', 'paused'].includes(
            m.delivery.outcome as string
          ))
      )
        throw new Error('Unavailable parent delivery');
      units += displayUnits([
        m.id,
        m.role as string,
        m.content,
        typeof m.name === 'string' ? m.name : '',
        typeof m.reasoning === 'string' ? m.reasoning : '',
        ...(m.citations === undefined ? [] : [literal(m.citations)]),
      ]);
      if (m.role === 'assistant') {
        const calls = m.toolCallIds ?? [];
        if (!Array.isArray(calls)) throw new Error('Unavailable declaration');
        for (const id of calls) {
          if (!text(id) || owners.has(id))
            throw new Error('Ambiguous tool ownership');
          owners.set(id, { id: m.id, index });
        }
      } else if (
        m.toolCallIds !== undefined &&
        (!Array.isArray(m.toolCallIds) || m.toolCallIds.length)
      )
        throw new Error('Unexpected declaration');
      if (m.role === 'tool' || m.toolCallId !== undefined) {
        if (
          m.role !== 'tool' ||
          !text(m.toolCallId) ||
          results.has(m.toolCallId)
        )
          throw new Error('Ambiguous result');
        results.set(m.toolCallId, { message: m, index });
      }
    }
    for (const t of tools) {
      if (
        !record(t) ||
        !text(t.id) ||
        !text(t.name) ||
        catalog.has(t.id) ||
        !Object.hasOwn(t, 'args') ||
        !['pending', 'running', 'complete', 'error'].includes(
          t.status as string
        )
      )
        throw new Error('Invalid tool catalog');
      catalog.set(t.id, t);
    }
    if (
      catalog.size !== owners.size ||
      [...owners.keys()].some((id) => !catalog.has(id))
    )
      throw new Error('Incomplete tool ownership');
    for (const m of messages) {
      if (!record(m) || m.role !== 'assistant') continue;
      const list: ToolCard[] = [];
      for (const id of (m.toolCallIds ?? []) as string[]) {
        const t = catalog.get(id),
          owner = owners.get(id),
          result = results.get(id);
        if (!t || !owner) throw new Error('Incomplete tool ownership');
        const rawStatus = record(data.rawStatuses)
          ? data.rawStatuses[id]
          : undefined;
        if (
          result &&
          (result.index <= owner.index ||
            messages
              .slice(owner.index + 1, result.index)
              .some(
                (v) =>
                  record(v) && (v.role === 'user' || v.role === 'assistant')
              ) ||
            (result.message.name !== t.name &&
              !(rawStatus === 'error' && result.message.name === undefined)))
        )
          throw new Error('Result crossed declaration batch');
        let resultText: string | undefined;
        if (t.status === 'complete') {
          if (
            !result ||
            typeof t.result !== 'string' ||
            result.message.content !== t.result
          )
            throw new Error('Unavailable resolved result');
          resultText = t.result;
        } else if (t.status === 'error') {
          if (typeof t.error !== 'string' || result)
            throw new Error('Unavailable tool error');
          resultText = t.error;
        } else if (
          result ||
          (Object.hasOwn(t, 'result') && t.result !== undefined)
        )
          throw new Error('Contradictory unresolved result');
        const kind = t.name === 'task' ? 'task' : 'tool';
        if (kind === 'task') taskCount++;
        const args = t.args,
          description =
            record(args) && typeof args.description === 'string'
              ? args.description
              : undefined,
          subagentType =
            record(args) && typeof args.subagent_type === 'string'
              ? args.subagent_type
              : undefined;
        const fields =
          kind === 'task' &&
          record(args) &&
          Object.keys(args).length === 2 &&
          text(description) &&
          ['field-researcher', 'weather-analyst', 'general-purpose'].includes(
            subagentType ?? ''
          )
            ? 'available'
            : 'unavailable';
        // Arguments can contain multiple individually bounded fields. The whole
        // literal representation remains subject to the conversation budget.
        const argumentsText = literal(args, LIMITS.displayText);
        if (argumentsText.length > LIMITS.displayText)
          throw new Error('Arguments display limit');
        if (
          rawStatus !== undefined &&
          rawStatus !== 'success' &&
          rawStatus !== 'error'
        )
          throw new Error('Unavailable raw result status');
        const card: ToolCard = Object.freeze({
          id,
          name: t.name as string,
          kind,
          fields,
          argumentsText,
          status: t.status as string,
          ...(description === undefined ? {} : { description }),
          ...(subagentType === undefined ? {} : { subagentType }),
          ...(resultText === undefined
            ? {}
            : { resultText: literal(resultText) }),
          ...(rawStatus === undefined ? {} : { resultStatus: rawStatus }),
        });
        units +=
          argumentsText.length +
          displayUnits([
            id,
            t.name as string,
            description ?? '',
            subagentType ?? '',
            resultText ?? '',
            card.fields,
            card.status,
            card.kind,
            (rawStatus as string) ?? '',
          ]);
        list.push(card);
      }
      if (list.length) {
        units += displayUnits([m.id as string]);
        cards.set(m.id as string, Object.freeze(list));
      }
    }
    if (
      [...results.keys()].some((id) => !catalog.has(id)) ||
      !budget.replace(scope, taskCount, 0, units)
    )
      throw new Error('Conversation observation limit');
    return Object.freeze({ status: 'available', taskCount, cards });
  } catch (e) {
    // A count from a partial or ambiguous catalog must not appear complete.
    return Object.freeze({
      status: 'incomplete',
      reason: e instanceof Error ? e.message : 'Unavailable parent data',
      taskCount: 0,
      cards: new Map<string, readonly ToolCard[]>(),
    });
  }
}

Inspect separate child observations

Expand Child response observations to read user, assistant and tool transcripts under their full namespace arrays. These are current-session observations from this open conversation. The preview does not guess a binding between a namespace and a parent task card from description text, arrival order or position. Identical descriptions therefore do not imply a shared or matched card.

Receiving means local delivery is active. Observed response means the local turn finished with a supported child observation; it is not checkpoint-confirmed child history. Stopped marks canceled local delivery. Incomplete marks unsupported, interrupted or uncertain child data. These labels describe the lifetime of local observations, not restored execution status.

Child observations remain visible across root checkpoint reconciliation and canonical root load. They are not restored historical children and are never labeled Saved child history. Reload and New clear them.

children.ts
import type { Message } from '@threadplane/core';
import {
  ConversationBudget,
  copyData,
  displayUnits,
  LIMITS,
  literal,
  record,
  sameData,
  text,
  validMessageMetadata,
} from './authority';
 
export type ChildStatus =
  | 'Receiving'
  | 'Observed response'
  | 'Stopped'
  | 'Incomplete';
export interface ChildRow {
  readonly key: string;
  readonly namespace: readonly string[];
  readonly text: string;
  readonly messages: readonly Message[];
  readonly status: ChildStatus;
}
export interface ChildContext {
  readonly owner: string;
  readonly turn: string;
  readonly generation: string;
}
interface Entry {
  readonly namespace: readonly string[];
  readonly messages: readonly Message[];
  readonly text: string;
  readonly interrupted: boolean;
}
interface TurnEntry {
  readonly turn: string;
  readonly generation: string;
  entries: Map<string, Entry>;
  status: ChildStatus;
  incomplete: boolean;
  ended: boolean;
}
 
/** App-owned observations survive native load. Namespace tuples are scoped by
 * local owner/turn/generation; no parent call identity is inferred. */
export class ChildObservations {
  private turns: TurnEntry[] = [];
  private current?: TurnEntry;
  constructor(
    private readonly owner: string,
    private readonly budget = new ConversationBudget()
  ) {}
  get incomplete(): boolean {
    return this.turns.some(
      (t) => t.incomplete || [...t.entries.values()].some((e) => e.interrupted)
    );
  }
  begin(turn: string, generation: string): boolean {
    if (
      !text(this.owner) ||
      !text(turn) ||
      !text(generation) ||
      [this.owner, turn, generation].some((s) => s.length > LIMITS.fieldText) ||
      this.turns.length >= LIMITS.namespaces ||
      this.turns.some((t) => t.turn === turn || t.generation === generation)
    )
      return false;
    if (this.current && !this.current.ended) this.finish('new');
    const next: TurnEntry = {
      turn,
      generation,
      entries: new Map(),
      status: 'Receiving',
      incomplete: false,
      ended: false,
    };
    this.turns.push(next);
    this.current = next;
    return true;
  }
  observe(input: unknown, context: ChildContext): boolean {
    const current = this.current;
    if (
      !current ||
      current.ended ||
      current.incomplete ||
      context.owner !== this.owner ||
      context.turn !== current.turn ||
      context.generation !== current.generation
    )
      return false;
    try {
      const children = copyData(input);
      if (!Array.isArray(children) || children.length > LIMITS.namespaces)
        throw new Error('Child observation limit');
      // Root loading happens only after finish; while active, disappearance is
      // uncertainty. An initial empty native snapshot is still a valid no-op.
      if (!children.length) {
        if (current.entries.size) throw new Error('Missing active children');
        return true;
      }
      const next = new Map(current.entries),
        batch = new Set<string>();
      for (const child of children) {
        if (
          !record(child) ||
          !Array.isArray(child.namespace) ||
          !child.namespace.length ||
          child.namespace.length > 16 ||
          child.namespace.some((s) => !text(s) || s.length > 1024) ||
          !Array.isArray(child.messages) ||
          !Array.isArray(child.interrupts) ||
          (child.values !== undefined && !record(child.values))
        )
          throw new Error('Unavailable child data');
        const namespace = child.namespace as string[],
          key = JSON.stringify(namespace);
        if (batch.has(key)) throw new Error('Duplicate namespace');
        batch.add(key);
        const prior = current.entries.get(key),
          messages = child.messages as Message[],
          ids = new Set<string>();
        for (const [index, m] of messages.entries()) {
          if (
            !record(m) ||
            !text(m.id) ||
            ids.has(m.id) ||
            !['user', 'assistant', 'tool', 'system'].includes(m.role) ||
            typeof m.content !== 'string' ||
            !validMessageMetadata(m) ||
            !record(m.delivery) ||
            m.delivery.generation !== current.generation ||
            !['streaming', 'complete'].includes(m.delivery.phase) ||
            (m.delivery.phase === 'complete' &&
              ![
                'success',
                'error',
                'aborted',
                'interrupted',
                'paused',
              ].includes(m.delivery.outcome)) ||
            (m.delivery.phase === 'streaming' &&
              Object.hasOwn(m.delivery, 'outcome') &&
              (m.delivery as Record<string, unknown>)['outcome'] !== undefined)
          )
            throw new Error('Unavailable child message');
          ids.add(m.id);
          displayUnits([
            m.id,
            m.content,
            m.name ?? '',
            m.reasoning ?? '',
            m.toolCallId ?? '',
            ...(m.toolCallIds ?? []),
          ]);
          const old = prior?.messages[index];
          if (old) {
            const grows = (
              a: readonly unknown[] | undefined,
              b: readonly unknown[] | undefined
            ) =>
              a === undefined ||
              (b !== undefined &&
                b.length >= a.length &&
                a.every((v, i) => sameData(v, b[i])));
            const stable = (a: unknown, b: unknown) =>
              a === undefined || sameData(a, b);
            if (
              old.delivery.phase === 'complete'
                ? !sameData(old, m)
                : old.id !== m.id ||
                  old.role !== m.role ||
                  !stable(old.name, m.name) ||
                  !stable(old.toolCallId, m.toolCallId) ||
                  !grows(old.toolCallIds, m.toolCallIds) ||
                  !grows(old.citations, m.citations) ||
                  (old.reasoning !== undefined &&
                    (m.reasoning === undefined ||
                      !m.reasoning.startsWith(old.reasoning))) ||
                  !m.content.startsWith(old.content)
            )
              throw new Error('Changed child observation');
          }
        }
        if (prior && messages.length < prior.messages.length)
          throw new Error('Shortened child observation');
        const transcript = messages
          .map(
            (m) =>
              `${m.role}${m.name ? ` (${m.name})` : ''}: ${m.content}${
                m.reasoning ? `\nReasoning: ${m.reasoning}` : ''
              }${m.citations ? `\nCitations: ${literal(m.citations)}` : ''}`
          )
          .join('\n\n');
        if (transcript.length > LIMITS.fieldText)
          throw new Error('Child transcript limit');
        next.set(
          key,
          Object.freeze({
            namespace,
            messages,
            text: transcript,
            interrupted:
              !!child.error ||
              child.interrupts.length > 0 ||
              messages.some(
                (m) =>
                  m.delivery.phase === 'complete' &&
                  m.delivery.outcome !== 'success'
              ),
          })
        );
      }
      for (const key of current.entries.keys())
        if (!batch.has(key)) throw new Error('Missing active namespace');
      let namespaces = 0,
        units = 0;
      for (const turn of this.turns)
        for (const entry of (turn === current ? next : turn.entries).values()) {
          namespaces++;
          const rowKey = JSON.stringify([
            this.owner,
            turn.turn,
            turn.generation,
            entry.namespace,
          ]);
          units += displayUnits([
            rowKey,
            ...entry.namespace,
            entry.text,
            ...entry.messages.flatMap((m) => [
              m.id,
              m.toolCallId ?? '',
              ...(m.toolCallIds ?? []),
            ]),
          ]);
          // All native message metadata kept in row is plain and capture-bounded;
          // display text is the transcript plus explicit namespace/key/status.
          units += Math.max('Observed response'.length, 'Incomplete'.length);
        }
      if (!this.budget.replace(`children:${this.owner}`, 0, namespaces, units))
        throw new Error('Conversation observation limit');
      current.entries = next;
      return true;
    } catch {
      current.incomplete = true;
      current.status = 'Incomplete';
      return false;
    }
  }
  finish(outcome: string): void {
    const current = this.current;
    if (!current || current.ended) return;
    current.ended = true;
    const unfinished = [...current.entries.values()].some(
      (e) =>
        !e.messages.length ||
        e.messages.some((m) => m.delivery.phase !== 'complete')
    );
    current.status =
      current.incomplete || (outcome === 'success' && unfinished)
        ? 'Incomplete'
        : outcome === 'success'
        ? 'Observed response'
        : outcome === 'aborted'
        ? 'Stopped'
        : 'Incomplete';
    if (current.status === 'Incomplete') current.incomplete = true;
  }
  rows(): readonly ChildRow[] {
    const rows: ChildRow[] = [];
    for (const turn of this.turns)
      for (const entry of turn.entries.values())
        rows.push(
          Object.freeze({
            key: JSON.stringify([
              this.owner,
              turn.turn,
              turn.generation,
              entry.namespace,
            ]),
            namespace: entry.namespace,
            text: entry.text,
            messages: entry.messages,
            status:
              turn.incomplete || entry.interrupted ? 'Incomplete' : turn.status,
          })
        );
    return Object.freeze(rows);
  }
}

Confirm the saved root conversation

Response saved. requires native success plus the actual owned raw current root state, a fresh history head and the exact checkpoint. The live and loaded canonical messages must match. The application performs canonical native load before permitting another Send. Model prose and child observations cannot supply this authority. A resolved root tool error can still be saved; its error card remains visible beside the saved root conversation.

Unsupported data or incomplete authority produces a notice, retains the prior confirmed root conversation and disables Send until New conversation. No inferred result is promoted to saved state.

The display bounds are cumulative across the open conversation: 100 task observations and 100 child namespaces, namespace depth 16 and components of at most 1024 UTF-16 code units, and at most 65,536 units per task description, result or joined child transcript. One shared 1,048,576-unit UTF-16 display-text ledger covers root messages, parent cards and children. Raw capture separately limits 10,000 nodes, depth 32 and 4 × 1024 × 1024 UTF-16 text units, including keys. Those structural limits can make a payload unavailable before it reaches 100 tasks or namespaces. Unsupported or oversized data stays visibly incomplete.

authority.ts
import type { Message, ToolCall } from '@threadplane/core';
 
/** All limits count UTF16 code units, including property keys. */
export const LIMITS = Object.freeze({
  depth: 32,
  nodes: 10000,
  copyText: 4 * 1024 * 1024,
  displayText: 1024 * 1024,
  fieldText: 65536,
  tasks: 100,
  namespaces: 100,
});
export const record = (v: unknown): v is Record<string, unknown> =>
  !!v && typeof v === 'object' && !Array.isArray(v);
export const text = (v: unknown): v is string =>
  typeof v === 'string' && v.length > 0;
 
/** Bounded capture before recursion or serialization. Never evaluates an accessor.
 * Enumerable keys are counted before the reflective own-key scan; descriptors
 * are inspected individually rather than allocating an unbounded descriptor map. */
export function copyData(value: unknown, allowUndefined = true): unknown {
  let nodes = 0,
    units = 0;
  const parents = new Set<object>();
  const charge = (s: string) => {
    units += s.length;
    if (units > LIMITS.copyText) throw new Error('Data text limit');
  };
  function visit(v: unknown, depth: number): unknown {
    if (++nodes > LIMITS.nodes || depth > LIMITS.depth)
      throw new Error('Data structure limit');
    if (v === null || typeof v === 'boolean') return v;
    if (typeof v === 'string') {
      charge(v);
      return v;
    }
    if (v === undefined && allowUndefined) return undefined;
    if (typeof v === 'number' && Number.isFinite(v)) return v;
    if (typeof v !== 'object' || v === null || parents.has(v))
      throw new Error('Nonplain data');
    const array = Array.isArray(v),
      proto = Object.getPrototypeOf(v);
    if (
      array
        ? proto !== Array.prototype
        : proto !== Object.prototype && proto !== null
    )
      throw new Error('Nonplain prototype');
    const length = array
      ? Object.getOwnPropertyDescriptor(v, 'length')?.value
      : undefined;
    if (
      array &&
      (!Number.isInteger(length) || length > LIMITS.nodes - nodes || length < 0)
    )
      throw new Error('Array limit');
    let enumerable = 0;
    for (const k in v)
      if (Object.hasOwn(v, k) && ++enumerable > LIMITS.nodes - nodes)
        throw new Error('Key limit');
    const keys = Reflect.ownKeys(v);
    if (keys.length > LIMITS.nodes - nodes + (array ? 1 : 0))
      throw new Error('Key limit');
    if (array && keys.length !== length + 1)
      throw new Error('Sparse or decorated array');
    const out: Record<string, unknown> | unknown[] = array
      ? []
      : Object.create(null);
    parents.add(v);
    for (const k of keys) {
      if (typeof k !== 'string') throw new Error('Symbol data');
      if (array && k === 'length') continue;
      if (array && (!/^(0|[1-9][0-9]*)$/.test(k) || Number(k) >= length))
        throw new Error('Decorated array');
      charge(k);
      const d = Object.getOwnPropertyDescriptor(v, k);
      if (!d || !('value' in d) || !d.enumerable)
        throw new Error('Accessor or hidden data');
      Object.defineProperty(out, k, {
        value: visit(d.value, depth + 1),
        enumerable: true,
      });
    }
    parents.delete(v);
    return Object.freeze(out);
  }
  return visit(value, 0);
}
 
/** Stable semantic equality for already-owned bounded plain data. */
export function sameData(a: unknown, b: unknown): boolean {
  if (a === b) return true;
  if (Array.isArray(a) && Array.isArray(b))
    return a.length === b.length && a.every((v, i) => sameData(v, b[i]));
  if (!record(a) || !record(b)) return false;
  const ak = Object.keys(a),
    bk = Object.keys(b);
  return (
    ak.length === bk.length &&
    ak.every((k) => Object.hasOwn(b, k) && sameData(a[k], b[k]))
  );
}
 
/** One ledger per local conversation. Retained scopes replace their own charge;
 * followups reuse this ledger, so repeated snapshots are never counted twice. */
export class ConversationBudget {
  private entries = new Map<
    string,
    { tasks: number; namespaces: number; units: number }
  >();
  replace(
    key: string,
    tasks: number,
    namespaces: number,
    units: number
  ): boolean {
    if (
      !text(key) ||
      key.length > LIMITS.fieldText ||
      [tasks, namespaces, units].some((n) => !Number.isInteger(n) || n < 0)
    )
      return false;
    let t = tasks,
      n = namespaces,
      u = units;
    for (const [k, v] of this.entries)
      if (k !== key) {
        t += v.tasks;
        n += v.namespaces;
        u += v.units;
      }
    if (
      t > LIMITS.tasks ||
      n > LIMITS.namespaces ||
      u > LIMITS.displayText ||
      (!this.entries.has(key) &&
        this.entries.size >= LIMITS.tasks + LIMITS.namespaces)
    )
      return false;
    this.entries.set(key, { tasks, namespaces, units });
    return true;
  }
}
export function displayUnits(strings: readonly string[]): number {
  let total = 0;
  for (const s of strings) {
    if (typeof s !== 'string') throw new Error('Unavailable display text');
    if (s.length > LIMITS.fieldText) throw new Error('Display field limit');
    total += s.length;
    if (total > LIMITS.displayText) throw new Error('Display aggregate limit');
  }
  return total;
}
/** Already-owned optional native metadata stays literal and contract-shaped. */
export function validMessageMetadata(m: Record<string, unknown>): boolean {
  if (
    ['name', 'reasoning', 'toolCallId'].some(
      (k) => m[k] !== undefined && typeof m[k] !== 'string'
    )
  )
    return false;
  if (
    m.toolCallIds !== undefined &&
    (!Array.isArray(m.toolCallIds) ||
      m.toolCallIds.some((v) => !text(v)) ||
      new Set(m.toolCallIds).size !== m.toolCallIds.length)
  )
    return false;
  if (
    m.citations !== undefined &&
    (!Array.isArray(m.citations) ||
      m.citations.some(
        (c) =>
          !record(c) ||
          !text(c.id) ||
          typeof c.index !== 'number' ||
          !Number.isFinite(c.index) ||
          ['title', 'url', 'snippet', 'sourceType', 'iconUrl'].some(
            (k) => c[k] !== undefined && typeof c[k] !== 'string'
          ) ||
          (c.publishedAt !== undefined &&
            typeof c.publishedAt !== 'string' &&
            typeof c.publishedAt !== 'number')
      ))
  )
    return false;
  return true;
}
/** Preflight encoded JSON size before allocating its representation. */
export function literal(
  value: unknown,
  maximum: number = LIMITS.fieldText
): string {
  const owned = copyData(value);
  if (!Number.isInteger(maximum) || maximum < 0 || maximum > LIMITS.displayText)
    throw new Error('Display limit');
  if (typeof owned === 'string') {
    if (owned.length > maximum) throw new Error('Display field limit');
    return owned;
  }
  let units = 0;
  const charge = (n: number) => {
    units += n;
    if (units > maximum) throw new Error('Display field limit');
  };
  const stringSize = (s: string) => {
    charge(s.length + 2);
    for (let i = 0; i < s.length; i++) {
      const c = s.charCodeAt(i);
      if (c === 34 || c === 92) charge(1);
      else if (c < 32) charge([8, 9, 10, 12, 13].includes(c) ? 1 : 5);
      else if (c >= 0xd800 && c <= 0xdbff) {
        const next = s.charCodeAt(i + 1);
        if (next >= 0xdc00 && next <= 0xdfff) i++;
        else charge(5);
      } else if (c >= 0xdc00 && c <= 0xdfff) charge(5);
    }
  };
  function size(v: unknown, depth: number): void {
    if (typeof v === 'string') {
      stringSize(v);
      return;
    }
    if (Array.isArray(v)) {
      charge(2);
      if (v.length) {
        charge(
          v.length + 1 + 2 * (depth + 1) * v.length + 2 * depth + v.length - 1
        );
        for (const entry of v)
          size(entry === undefined ? null : entry, depth + 1);
      }
    } else if (record(v)) {
      const keys = Object.keys(v).filter((k) => v[k] !== undefined);
      charge(2);
      if (keys.length) {
        charge(
          keys.length +
            1 +
            2 * (depth + 1) * keys.length +
            2 * depth +
            keys.length -
            1
        );
        for (const k of keys) {
          stringSize(k);
          charge(2);
          size(v[k], depth + 1);
        }
      }
    } else charge(v === undefined ? 'Unavailable'.length : String(v).length);
  }
  size(owned, 0);
  return JSON.stringify(owned, null, 2) ?? 'Unavailable';
}
 
export interface Baseline {
  readonly messages: readonly unknown[];
}
export interface TurnIdentity {
  readonly owner: string;
  readonly generation: string;
  readonly threadId: string;
  readonly humanId: string;
  readonly humanContent: string;
}
export interface Turn extends TurnIdentity {
  readonly baseline: Baseline;
}
export interface Checkpoint {
  readonly thread_id: string;
  readonly checkpoint_ns: '';
  readonly checkpoint_id: string;
  readonly checkpoint_map?: Readonly<Record<string, string>>;
}
export interface NativeSnapshot {
  readonly status: string;
  readonly messages: readonly Message[];
  readonly toolCalls: readonly ToolCall[];
  readonly values?: unknown;
  readonly interrupts: readonly unknown[];
  readonly subgraphs: readonly unknown[];
  readonly error?: unknown;
}
export interface AuthorityInput {
  readonly turn: Turn | undefined;
  readonly owner: string;
  readonly generation: string;
  readonly threadId: string;
  readonly outcome: string;
  /** Current root from the owned raw client, never child values or native synthesis. */
  readonly observedState: unknown;
  /** Fresh raw history from that same client and thread, newest first. */
  readonly history: readonly unknown[];
  /** Exact getState(thread, history[0].checkpoint), not a second current lookup. */
  readonly loadedCheckpoint: unknown;
  readonly snapshot: NativeSnapshot;
  readonly loadedSnapshot: NativeSnapshot;
}
export type Authority =
  | { readonly kind: 'unconfirmed' }
  | {
      readonly kind: 'terminal';
      readonly checkpoint: Checkpoint;
      readonly messages: readonly unknown[];
      readonly values: Readonly<Record<string, unknown>>;
      readonly rawToolStatuses: Readonly<Record<string, 'success' | 'error'>>;
      readonly turn: Turn;
    };
export function captureBaseline(
  input: unknown = { messages: [] }
): Baseline | undefined {
  try {
    const v = copyData(input, false);
    if (!record(v) || !Array.isArray(v.messages)) return undefined;
    return Object.freeze({ messages: v.messages });
  } catch {
    return undefined;
  }
}
export function captureTurn(
  baseline: Baseline | undefined,
  input: TurnIdentity
): Turn | undefined {
  try {
    const b = copyData(baseline, false),
      identity = copyData(input, false);
    if (
      !record(b) ||
      !Array.isArray(b.messages) ||
      !record(identity) ||
      ['owner', 'generation', 'threadId', 'humanId'].some(
        (k) =>
          !text(identity[k]) ||
          (identity[k] as string).length > LIMITS.fieldText
      ) ||
      typeof identity.humanContent !== 'string' ||
      identity.humanContent.length > LIMITS.fieldText ||
      b.messages.some((m) => record(m) && m.id === identity.humanId)
    )
      return undefined;
    return Object.freeze({ ...identity, baseline: b }) as unknown as Turn;
  } catch {
    return undefined;
  }
}
function checkpointSource(
  value: unknown,
  threadId: string
): Checkpoint | undefined {
  if (
    !record(value) ||
    value.thread_id !== threadId ||
    value.checkpoint_ns !== '' ||
    !text(value.checkpoint_id) ||
    value.checkpoint_id.length > LIMITS.fieldText ||
    Object.keys(value).some(
      (k) =>
        ![
          'thread_id',
          'checkpoint_ns',
          'checkpoint_id',
          'checkpoint_map',
        ].includes(k)
    )
  )
    return undefined;
  if (
    value.checkpoint_map !== undefined &&
    (!record(value.checkpoint_map) ||
      Object.keys(value.checkpoint_map).length !== 1 ||
      value.checkpoint_map[''] !== value.checkpoint_id)
  )
    return undefined;
  return value as unknown as Checkpoint;
}
const empty = (v: unknown) =>
  v === undefined || (Array.isArray(v) && v.length === 0);
const clean = (v: Record<string, unknown>) =>
  (v.error === undefined || v.error === null) &&
  empty(v.interrupts) &&
  empty(v.subgraphs) &&
  empty(v.__interrupt__);
 
/** Canonical root semantics independent of native delivery bookkeeping. */
function normalizedRaw(raw: readonly unknown[]):
  | {
      messages: Record<string, unknown>[];
      tools: Record<string, unknown>[];
      statuses: Record<string, 'success' | 'error'>;
    }
  | undefined {
  const messages: Record<string, unknown>[] = [],
    tools = new Map<string, Record<string, unknown>>(),
    ids = new Set<string>(),
    pending = new Set<string>(),
    statuses: Record<string, 'success' | 'error'> = Object.create(null);
  for (const w of raw) {
    if (
      !record(w) ||
      !text(w.id) ||
      ids.has(w.id) ||
      typeof w.content !== 'string' ||
      !clean(w) ||
      !empty(w.invalid_tool_calls) ||
      !['human', 'ai', 'tool'].includes(w.type as string)
    )
      return undefined;
    ids.add(w.id);
    const extra = w.additional_kwargs ?? {};
    if (
      !record(extra) ||
      !empty(extra.tool_calls) ||
      extra.function_call !== undefined
    )
      return undefined;
    const reasoning =
      typeof w.reasoning === 'string'
        ? w.reasoning
        : typeof extra.reasoning_content === 'string'
        ? extra.reasoning_content
        : undefined;
    // Supported citation shape is normalized explicitly; no private runtime imports.
    const source = extra.citations ?? extra.sources;
    let citations: unknown;
    if (Array.isArray(source))
      citations = source.map((s, i) => {
        if (typeof s === 'string')
          return { id: `c${i + 1}`, index: i + 1, url: s };
        const r = record(s) ? s : {},
          out: Record<string, unknown> = {
            id:
              [r.id, r.refId].find((x) => typeof x === 'string') ?? `c${i + 1}`,
            index:
              typeof r.index === 'number' && Number.isFinite(r.index)
                ? r.index
                : i + 1,
          };
        for (const [k, aliases] of Object.entries({
          title: ['title', 'name'],
          url: ['url', 'href', 'source'],
          snippet: ['snippet', 'content', 'excerpt'],
          sourceType: ['sourceType'],
          iconUrl: ['iconUrl'],
        })) {
          const v = aliases.map((a) => r[a]).find((x) => typeof x === 'string');
          if (v !== undefined) out[k] = v;
        }
        if (
          typeof r.publishedAt === 'string' ||
          typeof r.publishedAt === 'number'
        )
          out.publishedAt = r.publishedAt;
        if (record(r.extra)) out.extra = r.extra;
        return out;
      });
    const m: Record<string, unknown> = {
      id: w.id,
      role:
        w.type === 'human' ? 'user' : w.type === 'ai' ? 'assistant' : 'tool',
      content: w.content,
      ...(typeof w.name === 'string' ? { name: w.name } : {}),
    };
    if (reasoning !== undefined) m.reasoning = reasoning;
    if (citations !== undefined) m.citations = citations;
    if (w.type === 'ai') {
      if (pending.size || w.tool_call_id !== undefined) return undefined;
      const calls = w.tool_calls ?? [];
      if (!Array.isArray(calls)) return undefined;
      m.toolCallIds = [];
      for (const c of calls) {
        if (
          !record(c) ||
          !text(c.id) ||
          !text(c.name) ||
          tools.has(c.id) ||
          !Object.hasOwn(c, 'args') ||
          (c.type !== undefined && c.type !== 'tool_call') ||
          Object.keys(c).some(
            (k) => !['id', 'name', 'args', 'type'].includes(k)
          )
        )
          return undefined;
        (m.toolCallIds as string[]).push(c.id);
        tools.set(c.id, {
          id: c.id,
          name: c.name,
          args: c.args,
          status: 'pending',
        });
        pending.add(c.id);
      }
    } else if (w.type === 'tool') {
      const t =
        typeof w.tool_call_id === 'string'
          ? tools.get(w.tool_call_id)
          : undefined;
      if (
        !t ||
        !pending.delete(w.tool_call_id as string) ||
        !['success', 'error'].includes(w.status as string) ||
        (w.name !== t.name &&
          !(
            w.status === 'error' &&
            (w.name === null || w.name === undefined)
          )) ||
        !empty(w.tool_calls)
      )
        return undefined;
      m.toolCallId = w.tool_call_id;
      t.status = 'complete';
      t.result = w.content;
      statuses[w.tool_call_id as string] = w.status as 'success' | 'error';
    } else if (
      pending.size ||
      !empty(w.tool_calls) ||
      w.tool_call_id !== undefined
    )
      return undefined;
    messages.push(m);
  }
  if (
    pending.size ||
    messages[0]?.role !== 'user' ||
    messages.at(-1)?.role !== 'assistant' ||
    !text(messages.at(-1)?.content) ||
    (messages.at(-1)?.toolCallIds as unknown[])?.length
  )
    return undefined;
  return { messages, tools: [...tools.values()], statuses };
}
function nativeSemantic(
  input: NativeSnapshot
):
  | { messages: Record<string, unknown>[]; tools: readonly ToolCall[] }
  | undefined {
  if (
    input.status !== 'idle' ||
    (input.error !== undefined && input.error !== null) ||
    !Array.isArray(input.interrupts) ||
    input.interrupts.length ||
    !Array.isArray(input.subgraphs) ||
    !Array.isArray(input.messages) ||
    !Array.isArray(input.toolCalls)
  )
    return undefined;
  const messages: Record<string, unknown>[] = [],
    ids = new Set<string>();
  for (const m of input.messages) {
    if (
      !record(m) ||
      !text(m.id) ||
      ids.has(m.id) ||
      typeof m.content !== 'string' ||
      !validMessageMetadata(m) ||
      !record(m.delivery) ||
      !text(m.delivery.generation) ||
      m.delivery.phase !== 'complete' ||
      m.delivery.outcome !== 'success' ||
      Object.keys(m).some(
        (k) =>
          ![
            'id',
            'role',
            'content',
            'delivery',
            'reasoning',
            'citations',
            'name',
            'toolCallId',
            'toolCallIds',
          ].includes(k)
      )
    )
      return undefined;
    ids.add(m.id);
    const out: Record<string, unknown> = {
      id: m.id,
      role: m.role,
      content: m.content,
    };
    for (const k of ['name', 'reasoning', 'citations', 'toolCallId'])
      if (m[k as keyof Message] !== undefined) out[k] = m[k as keyof Message];
    if (m.role === 'assistant') {
      if (m.toolCallIds !== undefined && !Array.isArray(m.toolCallIds))
        return undefined;
      out.toolCallIds = m.toolCallIds ?? [];
    } else if (!empty(m.toolCallIds)) return undefined;
    messages.push(out);
  }
  return { messages, tools: input.toolCalls };
}
/** Only contiguous, complete result batches declared by a current assistant may
 * permute. Prior canonical history and all human/assistant boundaries stay strict. */
function matchesRoot(
  raw: ReturnType<typeof normalizedRaw>,
  native: ReturnType<typeof nativeSemantic>,
  prefix: number
): boolean {
  if (
    !raw ||
    !native ||
    raw.messages.length !== native.messages.length ||
    raw.tools.length !== native.tools.length
  )
    return false;
  const catalog = new Map<string, ToolCall>();
  for (const t of native.tools) {
    if (
      !record(t) ||
      !text(t.id) ||
      catalog.has(t.id) ||
      t.status !== 'complete'
    )
      return false;
    catalog.set(t.id, t);
  }
  for (const t of raw.tools)
    if (!sameData(t, catalog.get(t.id as string))) return false;
  for (let i = 0; i < raw.messages.length; i++) {
    const r = raw.messages[i],
      n = native.messages[i];
    if (!sameData(r, n)) return false;
    const callIds = r.role === 'assistant' ? (r.toolCallIds as string[]) : [];
    if (i >= prefix && callIds.length) {
      const batch = raw.messages.slice(i + 1, i + 1 + callIds.length),
        observed = native.messages.slice(i + 1, i + 1 + callIds.length);
      if (
        batch.length !== callIds.length ||
        observed.length !== callIds.length ||
        batch.some((m) => m.role !== 'tool') ||
        observed.some((m) => m.role !== 'tool')
      )
        return false;
      const byId = new Map(observed.map((m) => [m.toolCallId, m]));
      if (
        byId.size !== callIds.length ||
        batch.some(
          (m) =>
            !callIds.includes(m.toolCallId as string) ||
            !sameData(m, byId.get(m.toolCallId))
        )
      )
        return false;
      i += callIds.length;
    }
  }
  return true;
}
 
/** A failure yields no replacement: callers retain previously confirmed authority.
 * The caller captures its owned client and turn once before asynchronous reads. */
export function captureAuthority(input: unknown): Authority {
  const unavailable: Authority = Object.freeze({ kind: 'unconfirmed' });
  try {
    const captured = copyData(input) as AuthorityInput;
    const turn = captured.turn;
    if (
      !turn ||
      ['owner', 'generation', 'threadId', 'humanId'].some(
        (k) =>
          !text((turn as unknown as Record<string, unknown>)[k]) ||
          ((turn as unknown as Record<string, unknown>)[k] as string).length >
            LIMITS.fieldText
      ) ||
      typeof turn.humanContent !== 'string' ||
      turn.humanContent.length > LIMITS.fieldText ||
      captured.outcome !== 'success' ||
      turn.owner !== captured.owner ||
      turn.generation !== captured.generation ||
      turn.threadId !== captured.threadId ||
      !Array.isArray(turn.baseline?.messages)
    )
      return unavailable;
    const observed = copyData(captured.observedState, false),
      loaded = copyData(captured.loadedCheckpoint, false),
      history = copyData(captured.history, false);
    if (
      !record(observed) ||
      !record(loaded) ||
      !Array.isArray(history) ||
      !history.length ||
      !sameData(observed, loaded) ||
      !sameData(history[0], loaded) ||
      !clean(loaded) ||
      !Array.isArray(loaded.next) ||
      loaded.next.length ||
      !Array.isArray(loaded.tasks) ||
      loaded.tasks.length ||
      !record(loaded.values) ||
      !clean(loaded.values) ||
      !Array.isArray(loaded.values.messages)
    )
      return unavailable;
    const cp = checkpointSource(loaded.checkpoint, captured.threadId);
    if (!cp) return unavailable;
    const checkpointIds = new Set<string>();
    for (const h of history) {
      if (!record(h)) return unavailable;
      const c = checkpointSource(h.checkpoint, captured.threadId);
      if (!c || checkpointIds.has(c.checkpoint_id)) return unavailable;
      checkpointIds.add(c.checkpoint_id);
    }
    const messages = loaded.values.messages,
      prefix = turn.baseline.messages;
    if (!sameData(prefix, messages.slice(0, prefix.length))) return unavailable;
    const human = messages[prefix.length];
    if (
      !record(human) ||
      human.type !== 'human' ||
      human.id !== turn.humanId ||
      human.content !== turn.humanContent ||
      messages.filter((m) => record(m) && m.id === turn.humanId).length !== 1 ||
      messages
        .slice(prefix.length + 1)
        .some((m) => record(m) && m.type === 'human')
    )
      return unavailable;
    const raw = normalizedRaw(messages),
      before = nativeSemantic(captured.snapshot),
      after = nativeSemantic(captured.loadedSnapshot);
    const otherValues: Record<string, unknown> = {};
    for (const k of Object.keys(loaded.values))
      if (k !== 'messages' && k !== '__interrupt__')
        otherValues[k] = loaded.values[k];
    if (
      !raw ||
      !matchesRoot(raw, before, prefix.length) ||
      !matchesRoot(raw, after, prefix.length) ||
      !sameData(captured.snapshot.values, otherValues) ||
      !sameData(captured.loadedSnapshot.values, otherValues)
    )
      return unavailable;
    return Object.freeze({
      kind: 'terminal',
      checkpoint: cp,
      messages,
      values: loaded.values,
      rawToolStatuses: Object.freeze(raw.statuses),
      turn,
    });
  } catch {
    return unavailable;
  }
}
application.ts
import type {
  AgentSession,
  AgentStatus,
  CompleteOutcome,
} from '@threadplane/core';
import {
  createMessageContent,
  type MessageRow,
} from '@threadplane/content/messages';
import {
  captureAuthority,
  captureBaseline,
  captureTurn,
  copyData,
  record,
  sameData,
  LIMITS,
  ConversationBudget,
  type Baseline,
  type Turn,
  type Checkpoint,
  type NativeSnapshot,
} from './authority';
import {
  projectParentObservations,
  type ParentProjection,
  type ToolCard,
} from './projection';
import { ChildObservations, type ChildRow } from './children';
export interface SubagentsSession extends Omit<AgentSession, 'getSnapshot'> {
  getSnapshot(): NativeSnapshot;
  load(options?: { readonly signal?: AbortSignal }): Promise<void>;
}
export interface SubagentsClient {
  createThread(signal: AbortSignal): Promise<string>;
  sessionFactory(threadId: string): SubagentsSession;
  readCurrent(threadId: string, signal: AbortSignal): Promise<unknown>;
  readHistory(
    threadId: string,
    signal: AbortSignal
  ): Promise<readonly unknown[]>;
  readCheckpoint(checkpoint: Checkpoint, signal: AbortSignal): Promise<unknown>;
}
export interface SubagentsSnapshot {
  readonly threadId: string | null;
  readonly rows: readonly MessageRow[];
  readonly observations: ReadonlyMap<string, readonly ToolCard[]>;
  readonly taskCount: number | null;
  readonly childObservations: readonly ChildRow[];
  readonly activity:
    | 'idle'
    | 'creating'
    | 'running'
    | 'confirming'
    | 'replacing';
  readonly busy: boolean;
  readonly canSubmit: boolean;
  readonly outcome: CompleteOutcome | null;
  readonly error: string | null;
  readonly viewGeneration: number;
}
const initial = (viewGeneration = 0): SubagentsSnapshot =>
  Object.freeze({
    threadId: null,
    rows: [],
    observations: new Map(),
    taskCount: 0,
    childObservations: [],
    activity: 'idle',
    busy: false,
    canSubmit: true,
    outcome: null,
    error: null,
    viewGeneration,
  });
/** A local owner may author again only after exact raw authority and canonical load. */
export function createSubagentsApplication(client: SubagentsClient) {
  type Owner = {
    id: string;
    key: string;
    session: SubagentsSession;
    content: ReturnType<typeof createMessageContent>;
    budget: ConversationBudget;
    children: ChildObservations;
    baseline: Baseline;
    release?: () => void;
    closed: boolean;
    unsafe: boolean;
    loading: boolean;
    turn?: Turn;
    nativeGeneration?: string;
    text?: string;
    serial: string;
    completed: Map<string, unknown>;
    resolvedTools: Map<string, unknown>;
    projection?: ParentProjection;
    confirmed?: NativeSnapshot;
    rawStatuses?: Readonly<Record<string, 'success' | 'error'>>;
  };
  let owner: Owner | undefined,
    operation: AbortController | undefined,
    serial = 0,
    disposed = false;
  let snapshot = initial();
  const listeners = new Set<() => void>(),
    cleanups = new Set<Promise<void>>();
  let disposal: Promise<void> | undefined;
  const publish = (update: Partial<SubagentsSnapshot>) => {
    if (disposed) return;
    snapshot = Object.freeze({ ...snapshot, ...update });
    for (const f of listeners) f();
  };
  function bounded(work: () => void | Promise<void>) {
    let timer: ReturnType<typeof setTimeout>;
    return Promise.race([
      Promise.resolve()
        .then(work)
        .catch(() => undefined),
      new Promise<void>((resolve) => {
        timer = setTimeout(resolve, 2000);
      }),
    ]).finally(() => clearTimeout(timer));
  }
  function close(o: Owner) {
    if (o.closed) return Promise.resolve();
    o.closed = true;
    o.release?.();
    o.content.dispose();
    const p = bounded(() => o.session.dispose());
    cleanups.add(p);
    void p.finally(() => cleanups.delete(p));
    return p;
  }
  function detach() {
    const o = owner;
    owner = undefined;
    return o ? close(o) : Promise.resolve();
  }
  function blocked(o?: Owner) {
    o?.children.finish('error');
    if (o?.confirmed) {
      const projection = projectParentObservations(
        o.confirmed.messages,
        o.confirmed.toolCalls,
        o.budget,
        'parents',
        o.rawStatuses
      );
      if (projection.status === 'available')
        publish({
          rows: o.content.project({
            status: o.confirmed.status as AgentStatus,
            messages: o.confirmed.messages,
            toolCalls: o.confirmed.toolCalls,
          }),
          observations: projection.cards,
          taskCount: projection.taskCount,
        });
    }
    publish({
      canSubmit: false,
      outcome: 'error',
      error:
        'This conversation could not be confirmed. Start a new conversation to continue.',
      ...(o ? { childObservations: o.children.rows() } : {}),
    });
  }
  function read(o: Owner, valid: () => boolean): NativeSnapshot | undefined {
    if (!valid() || o.loading) return;
    try {
      const source = o.session.getSnapshot();
      if (!valid()) return;
      const s = copyData(source) as NativeSnapshot;
      if (
        !record(s) ||
        !Array.isArray(s.messages) ||
        !Array.isArray(s.toolCalls) ||
        !Array.isArray(s.subgraphs) ||
        !Array.isArray(s.interrupts)
      )
        throw Error('Unavailable');
      if (o.confirmed) {
        const prior = o.confirmed;
        if (
          s.messages.length < prior.messages.length ||
          prior.messages.some(
            (m, i) =>
              !sameData(
                { ...m, delivery: undefined },
                { ...s.messages[i], delivery: undefined }
              )
          ) ||
          prior.toolCalls.some(
            (t) =>
              !sameData(
                t,
                s.toolCalls.find((x) => x.id === t.id)
              )
          )
        )
          throw Error('Changed confirmed root');
      }
      if (
        o.text !== undefined &&
        s.messages.length > o.baseline.messages.length
      ) {
        const human = s.messages[o.baseline.messages.length];
        if (
          human.role !== 'user' ||
          human.content !== o.text ||
          !human.delivery?.generation ||
          s.messages
            .slice(o.baseline.messages.length + 1)
            .some((m) => m.role === 'user')
        )
          throw Error('Unowned turn');
        if (!o.turn) {
          o.turn = captureTurn(o.baseline, {
            owner: o.key,
            generation: o.serial,
            threadId: o.id,
            humanId: human.id,
            humanContent: o.text,
          });
          o.nativeGeneration = human.delivery.generation;
          if (!o.turn || !o.children.begin(o.serial, human.delivery.generation))
            throw Error('Unavailable turn');
        }
        if (
          human.id !== o.turn.humanId ||
          human.delivery.generation !== o.nativeGeneration ||
          s.messages
            .slice(o.baseline.messages.length)
            .some((m) => m.delivery?.generation !== o.nativeGeneration)
        )
          throw Error('Foreign generation');
        if (
          [...o.completed.keys()].some(
            (id) => !s.messages.some((m) => m.id === id)
          )
        )
          throw Error('Missing completed message');
        for (const tool of s.toolCalls) {
          const prior = o.resolvedTools.get(tool.id);
          if (prior && !sameData(prior, tool))
            throw Error('Changed resolved tool');
          if (tool.status === 'complete' || tool.status === 'error')
            o.resolvedTools.set(tool.id, tool);
        }
        if (
          [...o.resolvedTools.keys()].some(
            (id) => !s.toolCalls.some((t) => t.id === id)
          )
        )
          throw Error('Missing resolved tool');
        for (const m of s.messages) {
          const semantic = { ...m, delivery: undefined };
          const prior = o.completed.get(m.id);
          if (prior && !sameData(prior, semantic))
            throw Error('Changed completed message');
          if (m.delivery.phase === 'complete') o.completed.set(m.id, semantic);
        }
        if (
          !o.children.observe(s.subgraphs, {
            owner: o.key,
            turn: o.serial,
            generation: human.delivery.generation,
          })
        )
          throw Error('Incomplete children');
      } else if (s.subgraphs.length) throw Error('Unowned children');
      const projection = projectParentObservations(
        s.messages,
        s.toolCalls,
        o.budget
      );
      if (projection.status === 'available') {
        const rows = o.content.project({
          status: s.status as AgentStatus,
          messages: s.messages,
          toolCalls: s.toolCalls,
        });
        if (!valid()) return;
        o.projection = projection;
        publish({
          rows,
          observations: projection.cards,
          taskCount: projection.taskCount,
          childObservations: o.children.rows(),
        });
      } else publish({ taskCount: null, childObservations: o.children.rows() });
      if (!valid()) return;
      if (o.session.getSnapshot() !== source) throw Error('Changed source');
      return s;
    } catch {
      if (valid()) {
        o.unsafe = true;
        blocked(o);
      }
      return;
    }
  }
  async function submit(text: string) {
    if (disposed || snapshot.busy || !snapshot.canSubmit || !text.trim())
      return;
    if (text.length > LIMITS.fieldText) {
      blocked(owner);
      return;
    }
    const admitted = new AbortController();
    operation = admitted;
    const current = () =>
      !disposed && operation === admitted && !admitted.signal.aborted;
    publish({
      busy: true,
      canSubmit: false,
      activity: owner ? 'running' : 'creating',
      outcome: null,
      error: null,
    });
    try {
      if (!current()) return;
      if (!owner) {
        const id = await client.createThread(admitted.signal);
        if (!current()) return;
        if (typeof id !== 'string' || !id || id.length > LIMITS.fieldText)
          throw Error('Unknown thread');
        const session = client.sessionFactory(id),
          budget = new ConversationBudget(),
          key = String(snapshot.viewGeneration);
        const o: Owner = {
          id,
          key,
          session,
          budget,
          children: new ChildObservations(key, budget),
          content: createMessageContent(),
          baseline: { messages: [] },
          closed: false,
          unsafe: false,
          loading: false,
          serial: '',
          completed: new Map(),
          resolvedTools: new Map(),
        };
        if (!current()) {
          void close(o);
          return;
        }
        owner = o;
        o.release = session.subscribe(() => {
          const active = operation;
          if (active && !o.loading)
            read(
              o,
              () =>
                !disposed &&
                operation === active &&
                !active.signal.aborted &&
                owner === o &&
                !o.closed
            );
        });
        publish({ threadId: id, activity: 'running' });
      }
      const o = owner,
        valid = () => current() && owner === o && !o.closed;
      o.serial = String(++serial);
      o.text = text;
      o.turn = undefined;
      o.nativeGeneration = undefined;
      o.completed = new Map();
      o.resolvedTools = new Map();
      if (!valid()) return;
      const before = read(o, valid);
      if (!valid()) return;
      if (
        !before ||
        before.messages.length !== o.baseline.messages.length ||
        before.subgraphs.length ||
        o.unsafe
      )
        throw Error('Unowned baseline');
      const outcome = await o.session.submit(text, { signal: admitted.signal });
      if (!valid()) return;
      const live = read(o, valid);
      if (!valid()) return;
      if (!live || o.unsafe || outcome !== 'success' || !o.turn)
        throw Error('Unconfirmed delivery');
      publish({ activity: 'confirming' });
      if (!valid()) return;
      const observedState = copyData(
        await client.readCurrent(o.id, admitted.signal),
        false
      );
      if (!valid()) return;
      const history = copyData(
        await client.readHistory(o.id, admitted.signal),
        false
      );
      if (!valid()) return;
      if (!Array.isArray(history) || !record(history[0]))
        throw Error('Unavailable history');
      const requested = copyData(history[0].checkpoint, false) as Checkpoint;
      const loadedCheckpoint = copyData(
        await client.readCheckpoint(requested, admitted.signal),
        false
      );
      if (!valid()) return;
      o.children.finish(outcome);
      o.loading = true;
      await o.session.load({ signal: admitted.signal });
      if (!valid()) return;
      const source = o.session.getSnapshot(),
        loaded = copyData(source) as NativeSnapshot;
      if (!valid()) return;
      const authority = captureAuthority({
        turn: o.turn,
        owner: o.key,
        generation: o.serial,
        threadId: o.id,
        outcome,
        observedState,
        history,
        loadedCheckpoint,
        snapshot: live,
        loadedSnapshot: loaded,
      });
      if (authority.kind !== 'terminal' || o.children.incomplete)
        throw Error('Unconfirmed authority');
      const projection = projectParentObservations(
        loaded.messages,
        loaded.toolCalls,
        o.budget,
        'parents',
        authority.rawToolStatuses
      );
      if (projection.status !== 'available')
        throw Error('Unavailable final display');
      const rows = o.content.project({
        status: loaded.status as AgentStatus,
        messages: loaded.messages,
        toolCalls: loaded.toolCalls,
      });
      if (!valid()) return;
      const latest = o.session.getSnapshot();
      if (!valid()) return;
      if (latest !== source) throw Error('Changed source before publication');
      o.confirmed = loaded;
      o.rawStatuses = authority.rawToolStatuses;
      const baseline = captureBaseline({ messages: authority.messages });
      if (!baseline) throw Error('Unavailable baseline');
      o.baseline = baseline;
      o.text = undefined;
      o.turn = undefined;
      o.loading = false;
      o.projection = projection;
      publish({
        rows,
        observations: projection.cards,
        taskCount: projection.taskCount,
        childObservations: o.children.rows(),
        outcome,
        canSubmit: true,
      });
    } catch {
      if (current()) {
        if (owner) owner.unsafe = true;
        blocked(owner);
      }
    } finally {
      if (current()) {
        operation = undefined;
        publish({ busy: false, activity: 'idle' });
      }
    }
  }
  async function stop() {
    if (disposed || !operation) return;
    operation.abort();
    operation = undefined;
    const o = owner;
    o?.children.finish('aborted');
    const children = o?.children.rows() ?? snapshot.childObservations;
    const p = detach();
    publish({
      busy: false,
      canSubmit: false,
      activity: 'idle',
      outcome: 'aborted',
      error: null,
      childObservations: children,
    });
    await Promise.all([p, bounded(() => o?.session.stop())]);
  }
  async function newConversation() {
    if (disposed) return;
    operation?.abort();
    operation = undefined;
    owner?.children.finish('new');
    const p = detach();
    snapshot = initial(snapshot.viewGeneration + 1);
    publish({});
    await p;
  }
  function dispose() {
    if (disposal) return disposal;
    disposed = true;
    operation?.abort();
    operation = undefined;
    void detach();
    listeners.clear();
    return (disposal = Promise.all([...cleanups]).then(() => undefined));
  }
  return {
    getSnapshot: () => snapshot,
    subscribe: (f: () => void) => {
      if (disposed) return () => undefined;
      listeners.add(f);
      return () => listeners.delete(f);
    },
    submit,
    stop,
    newConversation,
    dispose,
  };
}

Stop and start again

Stop cancels local delivery and preserves prior confirmed root content. It does not roll back backend work. Start a New conversation before sending again after Stop or uncertain confirmation. There is no automatic resume, replay or retry.

Build from source

From a Threadplane checkout with dependencies installed:

npx nx build cockpit-deep-agents-subagents-react
npx nx e2e cockpit-deep-agents-subagents-react

Browser verification uses the actual compiled Python graph, installed native SDK and deterministic model construction. Fixtures, proof endpoints and verification controls are excluded from public Code and the production frontend.

Looking for something specific?