Agent harness

Define agents in YAML, run them in terminal, web, or headless API mode, persist sessions, and delegate work to sub-agents — all without writing workflow code.

The agent harness is the operator-facing layer of Flux’s agent system. It lets you define an agent in YAML, register it in Flux, and run it as an interactive CLI chat, a browser UI, or a headless SSE API — without writing a workflow.

This is different from the agent primitive, which is a Python @task you call inside a workflow. The harness wraps that primitive in session management, a serving process, and CRUD commands so you can run agents from the command line and expose them over HTTP. Each session is a standard Flux workflow execution (agents/agent_chat) under the hood, so replay, pause/resume, RBAC, and observability all apply automatically.

How it works

flux agent start starts a new execution of the agents/agent_chat workflow on a connected worker. The worker reads the agent definition from the config store, calls agent() with those settings, and pauses waiting for user input. Each user message is a workflow resume. The session ID and the Flux execution ID are the same value — there is no separate session store.

A minimal agent needs three fields:

# assistant.yaml
name: assistant
model: anthropic/claude-sonnet-4-20250514
system_prompt: |
  You are a helpful coding assistant. Keep answers concise.
flux agent create assistant --file assistant.yaml
flux agent start assistant --mode terminal
# /quit or Ctrl+D exits; the session ID is printed so you can resume later:
flux agent session resume <session-id>

YAML schema

Every field maps to a column in the agents table and to AgentDefinition in flux.agents.types. Three fields are required; the rest have defaults.

name: coder                              # required — primary key
model: anthropic/claude-sonnet-4-20250514  # required — provider/model format
system_prompt: |                         # required
  You are a senior engineer. Be concise.
description: Coding assistant

tools:                                   # built-in tool groups (see Tools)
  - system_tools:
      workspace: .
      timeout: 60
      max_output_chars: 200000

tools_file: ./custom_tools.py            # @task functions as tools
workflow_file: ./custom_chat.py          # custom workflow (advanced)

mcp_servers:
  - url: https://mcp.github.example.com
    name: github
    secret: GITHUB_TOKEN                 # resolved from flux secrets at runtime

skills_dir: ./skills                     # stored inline at create time
agents:                                  # sub-agents (must already exist)
  - researcher

planning: true
max_plan_steps: 20
approve_plan: false
max_tool_calls: 20
max_concurrent_tools: 4
max_tokens: 4096
stream: true
approval_mode: default                   # default | autonomous
reasoning_effort: medium                 # low | medium | high

long_term_memory:
  provider: sqlite                       # sqlite | postgresql
  connection: memory.db
  scope: "user:default"

Working memory (turn-by-turn context) is always on with a window of 50 turns.

model must be in provider/model_name format. reasoning_effort is low, medium, or high. long_term_memory.connection is required when the block is present.

tools_file, workflow_file, and skills_dir are read from the machine running flux agent create and stored inline in the database. Workers load them from the database, not the local filesystem.

Tools

The resolver (flux.agents.tools_resolver) recognizes system_tools (all four groups: shell, files, search, directory), plus shell, files, search, and directory individually. Use them as bare strings for defaults, or as dict keys to set workspace, timeout, max_output_chars, and blocklist. For custom Python tools, set tools_file to a .py file whose top-level @task functions become tools; the file is stored inline at create time. Any unrecognized group name raises Unknown tool group.

MCP servers

mcp_servers:
  - url: https://mcp.github.example.com
    name: github
    secret: GITHUB_TOKEN

At runtime the worker connects, discovers tools, and adds them to the agent’s tool list. secret is a Flux secret key resolved at runtime via SecretManager. When an MCP server requires user authorization, the workflow pauses with an elicitation payload. Terminal mode prints the URL and prompts you to open a browser; web mode shows a clickable link; API mode emits a {"type": "elicitation", ...} SSE event for the client to handle.

Serving modes

Terminal

flux agent start assistant --mode terminal

Runs an interactive readline loop. Tokens stream inline. Type /session to print the current session ID, /help for commands, /quit or Ctrl+D to exit. The session ID is printed on exit. By default Flux uses a Textual TUI when your terminal supports it; --plain falls back to basic ANSI output.

Web

flux agent start assistant --mode web --port 8080

Serves a single-page chat UI at http://localhost:8080. Intended for one operator at a time. The Flux auth token is resolved at process start; browsers do not need to send a Bearer token. If you want to expose this beyond localhost, put a reverse proxy in front and enforce access control there — web mode does not authenticate individual browser requests.

--host sets the bind address (default 127.0.0.1). Use --host 0.0.0.0 to expose on all interfaces.

API (headless)

flux agent start assistant --mode api --port 8080

Headless SSE service. Every request except GET /health requires Authorization: Bearer <token>, passed through to the Flux server. Endpoints: POST /chat (new session), POST /chat?session=<id> (resume), POST /elicitation/{id}?session=<id> (MCP auth), GET /session/{id} (execution proxy).

curl -N -X POST http://localhost:8080/chat \
  -H "Authorization: Bearer $FLUX_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"message": "list the files in /tmp"}'

SSE frames are JSON objects. chat_response is serialized as type: response on the wire for compatibility with the bundled web UI:

{"type": "session_id", "id": "7f3c2d1a-..."}
{"type": "token", "text": "Hello"}
{"type": "tool_start", "name": "shell", "args": {"cmd": "ls"}}
{"type": "tool_done", "name": "shell", "status": "success"}
{"type": "response", "content": "Here are the files...", "turn": 1}
{"type": "session_end", "reason": "user_exit", "turns": 3}

Sessions

A session is a running execution of agent_chat. The session ID and the Flux execution ID are the same value.

flux agent session list [<agent-name>]    # list sessions
flux agent session show <session-id>      # execution details
flux agent session resume <session-id>    # attach in terminal mode (no agent name needed)
flux agent stop <session-id>              # cancel the execution

Sessions are not user-scoped. Any principal with agent:<name>:start and workflow:agents:agent_chat:run can resume any session.

Sub-agent delegation

List agent names in the agents field to let one agent delegate to others:

name: lead
model: anthropic/claude-sonnet-4-20250514
system_prompt: |
  Delegate coding tasks to "assistant", research tasks to "researcher".
agents:
  - assistant
  - researcher
planning: true

Each listed agent must already exist in Flux. At runtime the worker wraps each one as a workflow_agent tool — calling it starts a separate agent_chat execution and returns its output to the lead.

flux agent create assistant --file examples/agents/assistant.yaml
flux agent create researcher --file examples/agents/researcher.yaml
flux agent create lead --file examples/agents/delegation.yaml
flux agent start lead --mode terminal

CLI reference

flux agent create <name> --file agent.yaml    # create from YAML
flux agent create <name> \                    # or from flags
  --model provider/name \
  --system-prompt TEXT \
  --tools NAME \
  --planning

flux agent list
flux agent show <name> [--format simple|json|yaml]
flux agent update <name> --file agent.yaml    # flags win over YAML; unset fields preserved
flux agent delete <name>

flux agent start <name> \
  [--mode terminal|web|api] \     # default: terminal
  [--session SESSION_ID] \        # resume an existing session
  [--port PORT] \                 # web/api only, default 8080
  [--host HOST] \                 # default 127.0.0.1
  [--plain]                       # ANSI output, no TUI

--mcp-server on create records only the URL. Auth, secrets, and name require --file. The auth token is resolved from $FLUX_AUTH_TOKEN first, then from flux auth login credentials.

Troubleshooting

401 on start — run flux auth login or set FLUX_AUTH_TOKEN.

Missing or invalid Authorization header on API /chat — API mode requires a per-request Bearer token. The operator token set at process start is not used for API requests.

MCP server keeps prompting for authorization — elicitation state is not cached across sessions. Accept once and keep the session alive for the duration of the work.

Session won’t resume — the execution may have ended. Check GET /executions/<session-id>; if the state is COMPLETED, CANCELLED, or FAILED, start a new session.

Tools are never called — verify approval_mode and check that the principal has workflow:agents:agent_chat:task:<task_name>:execute.

Unknown tool group — the resolver only recognizes system_tools, shell, files, search, and directory. Anything else goes in a Python file via tools_file.