Open Harness

Signal

Signal type reference

Signal

All communication in Open Harness uses typed signals.

Type Definition

interface Signal {
  id: string;           // Unique identifier ("sig_abc123")
  name: string;         // Signal name ("analysis:complete")
  payload: unknown;     // Signal-specific data
  timestamp: string;    // ISO timestamp
  source?: {
    agent?: string;     // Emitting agent
    parent?: string;    // Parent signal ID (causality)
  };
}

Signal ID Format

Signal IDs use the format sig_<random>:

"sig_abc123def456"

IDs are unique within a workflow execution.

Signal Names

Signal names use colon-separated namespaces:

NamespaceExamples
workflow:workflow:start, workflow:end
agent:agent:activated, agent:completed
harness:harness:start, harness:end
text:text:delta, text:complete
thinking:thinking:delta, thinking:complete
tool:tool:call, tool:result
state:state:analysis:changed
Customanalysis:complete, review:done

Payload Types

Workflow Signals

// workflow:start
{ state: TState; agents: string[] }

// workflow:end
{
  state: TState;
  metrics: { durationMs: number; activations: number };
  terminatedEarly: boolean;
}

Agent Signals

// agent:activated
{ agent: string; triggeredBy: string }

// agent:skipped
{ agent: string; reason: string }

// agent:completed
{ agent: string; output: unknown }

Harness Signals

// harness:start
{ model: string }

// harness:end
{
  usage?: { inputTokens: number; outputTokens: number };
  cost?: number;
  sessionId?: string;
}

// harness:error
{ error: string; retryAfter?: number }

Text Signals

// text:delta
{ content: string }

// text:complete
{ content: string }

Thinking Signals

// thinking:delta
{ content: string }

// thinking:complete
{ content: string }

Tool Signals

// tool:call
{ id: string; name: string; input: unknown }

// tool:result
{ id: string; name: string; output: unknown; error?: string }

State Signals

// state:{key}:changed
{ value: unknown; previousValue: unknown }

Source Tracking

The source field tracks signal origin:

{
  name: "analysis:complete",
  source: {
    agent: "analyzer",       // Which agent emitted
    parent: "sig_xyz789",    // Parent signal (trigger)
  },
}

Use for causality debugging:

const chain = result.signals.filter(s => s.source?.parent === targetId);

Creating Signals

Harnesses create signals using createSignal:

import { createSignal } from "@open-harness/core";

yield createSignal("harness:start", { model: "my-model" });
yield createSignal("text:delta", { content: "Hello" });
yield createSignal("harness:end", { usage: { inputTokens: 10, outputTokens: 5 } });

Working with Signals

Filter by Name

const agentSignals = result.signals.filter(s => s.name.startsWith("agent:"));

Extract Text

const textComplete = result.signals.find(s => s.name === "text:complete");
const output = textComplete?.payload.content;

Calculate Totals

const totalTokens = result.signals
  .filter(s => s.name === "harness:end")
  .reduce((sum, s) => {
    const usage = s.payload.usage || { inputTokens: 0, outputTokens: 0 };
    return sum + usage.inputTokens + usage.outputTokens;
  }, 0);

Type Guards

Use type guards for type-safe access:

function isHarnessEnd(signal: Signal): signal is Signal & { payload: { usage: Usage } } {
  return signal.name === "harness:end";
}

const harnessEnds = result.signals.filter(isHarnessEnd);

See Also

On this page