Role Precedence Rules in Flue: Harness, Session, and Call Levels Explained

Flue applies strict role precedence as call-level > session-level > harness-level, where the narrowest scope always wins and acts as a temporary system-prompt overlay for that specific interaction.

Flue, Astro's LLM orchestration framework, enables developers to assign specialized AI personas through role definitions at three distinct scopes. Understanding how role precedence rules govern the interaction between harness, session, and call-level assignments ensures your agents adopt the correct context for every specific task without leaking instructions into persistent conversation history.

The Three Scopes of Role Assignment

Flue allows role assignment at three distinct levels of granularity. Each scope serves a different purpose in the agent lifecycle, from global defaults to single-request overrides.

Harness-Level Roles

The harness represents your global agent configuration initialized via init(). When you provide a role during harness creation, it serves as the default fallback for all subsequent operations.

In packages/sdk/src/harness.ts, the init() function validates the supplied role using assertRoleExists (lines 70–77), ensuring the role is defined in your agent's AGENTS.md or roles/ directory before the harness is created.

const harness = await init({
  model: 'anthropic/claude-sonnet-4',
  role: 'coder',  // Fallback role for all calls
});

Session-Level Roles

A session maintains stateful conversation context. When you create a session via harness.session(), you can specify a role that overrides the harness default for all calls within that session's lifetime.

The validation occurs in packages/sdk/src/session.ts (lines 379–387), where assertRoleExists checks the session-level role before opening the connection.

const session = await harness.session('review-thread', {
  role: 'reviewer',  // Overrides harness role for this session
});

Call-Level Roles

Individual calls—whether session.prompt(), session.task(), or session.skill()—accept a role option that takes precedence over both session and harness configurations. This role applies only to that specific request.

According to the source code, when the call executes, Flue merges this role into the request's system prompt, superseding any broader scope assignments.

await session.prompt(
  'Review the latest changes.',
  { role: 'auditor' }  // Highest precedence: applies only to this prompt
);

How Role Precedence Works

The precedence rule follows a simple hierarchical cascade: call role > session role > harness role. When processing a request, Flue evaluates roles from most specific to least specific, selecting the first available assignment.

Crucially, the chosen role is applied as a call-scoped system-prompt overlay. This means:

  • The role instructions are visible to the model only for the duration of that specific call
  • The role does not become part of the persistent message history or user-message chain
  • Subsequent calls revert to the next applicable level in the hierarchy (session or harness)

This architecture prevents role contamination between different phases of a workflow, allowing seamless persona switching without historical baggage.

Implementation in the Source Code

The precedence hierarchy is enforced through explicit validation steps and strategic merging logic across three key files:

packages/sdk/src/harness.ts: Validates harness-level roles during initialization and serves as the fallback provider when narrower scopes don't specify roles.

packages/sdk/src/session.ts: Handles session-level role validation and propagates the active role context to individual calls, serving as the intermediary between harness defaults and call-specific overrides.

packages/sdk/src/roles.ts: Defines the role structure and implements assertRoleExists, the validation utility used across all three scopes to ensure only defined roles can be assigned.

The README.md documents this behavior explicitly: "Roles can be set at the harness, session, or call level. Precedence is call role > session role > harness role. Role instructions are applied as call-scoped system prompt overlays, not injected into the persisted user message history."

Practical Examples

This example demonstrates all three precedence levels in a single workflow:

// Initialize with harness-level fallback role
const harness = await init({
  model: 'anthropic/claude-sonnet-4',
  role: 'coder',  // Default for all operations
});

// Create session with reviewer persona
const session = await harness.session('code-review', {
  role: 'reviewer',  // Overrides 'coder' for this session
});

// First prompt uses call-level auditor role
await session.prompt(
  'Audit the authentication logic for security vulnerabilities.',
  { role: 'auditor' }  // Overrides 'reviewer' for this call only
);

// Second prompt falls back to session-level reviewer role
await session.prompt(
  'Provide general feedback on code style.'
  // No call-level role specified, uses 'reviewer'
);

// Task with its own specialized role
const research = await session.task(
  'Research related CVEs.',
  { role: 'security-researcher' }  // Overrides session role for this task
);

Execution flow:

  • The first prompt executes with the auditor role
  • The second prompt executes with the reviewer role (session-level)
  • The task executes with the security-researcher role
  • Any subsequent calls on this session without explicit roles would use reviewer

Summary

  • Role precedence in Flue follows a strict hierarchy: call-level roles override session-level roles, which override harness-level roles.
  • Validation occurs at creation: assertRoleExists in packages/sdk/src/roles.ts validates roles when initializing harnesses or sessions.
  • Call-level roles are ephemeral: They exist as temporary system-prompt overlays for single requests and do not persist in conversation history.
  • Scope-specific flexibility: You can maintain a default persona at the harness level, override it for specific conversation threads at the session level, and further specialize for individual prompts or tasks.

Frequently Asked Questions

What happens if I don't specify a role at the call level?

If you omit the role option in session.prompt() or session.task(), Flue automatically falls back to the session-level role configured when calling harness.session(). If no session-level role exists, it uses the harness-level role from init(). The system always selects the most specific available role in the hierarchy.

Does a call-level role persist in the session conversation history?

No. According to the Flue source code, call-level roles are applied as temporary system-prompt overlays visible only during that specific request. They are not injected into the persisted user message chain, ensuring that specialized instructions don't leak into subsequent interactions or the agent's memory.

How does Flue validate that a role exists before applying it?

Flue uses the assertRoleExists utility defined in packages/sdk/src/roles.ts to validate roles during harness creation (packages/sdk/src/harness.ts, lines 70–77) and session creation (packages/sdk/src/session.ts, lines 379–387). This ensures that only roles defined in your agent's AGENTS.md or roles/ directory can be assigned, preventing runtime errors from undefined personas.

Can I override a session role for just one message without affecting the rest of the session?

Yes. By passing the role option to an individual session.prompt() or session.task() call, you override the session-level role for that specific interaction only. After that call completes, subsequent calls on the same session revert to using the session-level role (or harness-level fallback if none was set at the session level).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →