Open Harness
Concepts

Template Expressions

Access state in agent prompts with template syntax

Template Expressions

Open Harness uses a simple template syntax to inject state into agent prompts.

Syntax

Expressions are wrapped in double curly braces:

prompt: "Analyze this input: {{ state.input }}"

State Access

Access any field from your typed state:

type State = {
  user: { name: string; email: string };
  task: string;
  previousResult: string | null;
};

const { agent } = createWorkflow<State>();

const myAgent = agent({
  prompt: `User: {{ state.user.name }}
Email: {{ state.user.email }}
Task: {{ state.task }}
Previous: {{ state.previousResult }}`,
  activateOn: ["workflow:start"],
});

Nested Access

Access deeply nested properties:

prompt: `
Analysis: {{ state.analysis.summary }}
Score: {{ state.analysis.scores.overall }}
`

Array Access

Access array elements by index:

prompt: `
First item: {{ state.items[0] }}
Last result: {{ state.history[state.history.length - 1] }}
`

In Guard Conditions

The when guard receives a typed context object:

const reviewer = agent({
  activateOn: ["code:complete"],
  when: (ctx) => {
    // Full TypeScript autocomplete
    return ctx.state.code !== null && ctx.state.code.length > 0;
  },
});

Type Safety

The template syntax is evaluated at runtime, but your State type provides compile-time safety in:

  • when guards (full TypeScript checking)
  • endWhen conditions (full TypeScript checking)
  • updates field (must be keyof State)
type State = { result: string | null };
const { agent } = createWorkflow<State>();

const myAgent = agent({
  updates: "result",     // ✓ Type-checked: keyof State
  // updates: "invalid", // ✗ Type error: not in State
});

Best Practices

Keep Prompts Readable

// ✓ Good: Clear structure
prompt: `
Task: {{ state.task }}

Context:
{{ state.context }}

Previous feedback:
{{ state.feedback }}
`

// ✗ Avoid: Dense inline expressions
prompt: "Do {{ state.task }} with {{ state.context }} considering {{ state.feedback }}"

Handle Null Values

Templates render null as the string "null". Consider your prompts:

// If state.previousResult might be null:
prompt: `
Previous result: {{ state.previousResult }}
`
// Renders as: "Previous result: null"

// Better: Use when guard to control activation
when: (ctx) => ctx.state.previousResult !== null,

Next Steps

On this page