Agent Harness¶
The agent harness turns LLM agents into first-class Flux resources. An agent is a YAML-declarable entity stored in the database, serves as a chat interface over the terminal, a web UI, or a headless HTTP/SSE API, and reuses Flux's existing workflow engine, RBAC, streaming, and pause/resume mechanics under the hood.
If you are familiar with the Python-level agent() task (see AI Agents), the harness is the layer above it: it packages an agent definition, a chat loop, session management, and a user-facing process into something an operator can create and run without writing code.
Overview¶
A Flux agent is a row in the agents table plus a process (flux agent start) that drives a chat session against the server. The chat loop itself runs as a normal Flux workflow (agents/agent_chat) on a worker. The agent process only handles the UX.
Capabilities:
- Declarative agents — define an agent in YAML, store it in Flux, reuse it anywhere.
- Three serving modes —
terminalfor interactive CLI use,webfor a local single-user chat page,apifor headless integration. - Tool integration — bundled system tools (shell, files, search, directory), individual groups, cherry-picked names, or custom Python
@tasktools from a file. - MCP integration — connect to any MCP server; URL-based elicitation (auth) is handled across all three serving modes.
- Elicitation — MCP
elicitation/createrequests pause the workflow; the process shows the URL and resumes on completion. - Working memory — turn-by-turn conversation memory, always on.
- Planning — optional structured planning with approval; see Agent Plans.
- Delegation — agents can call other agents as sub-agents; see Sub-agents.
- Skills — reusable prompt capabilities loaded from a directory; see Agent Skills.
- Long-term memory — optional provider-backed memory across sessions; see AI Memory.
- Sessions are executions — a session ID is a Flux execution ID; any authorized user can resume any session.
Architecture¶
┌─────────────────────────────────────────────────────┐
│ flux agent start │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │
│ │Terminal │ │ Web UI │ │ API (SSE) │ │
│ │ │ │ (chat │ │ │ │
│ │ │ │ page) │ │ POST /chat │ │
│ └────┬────┘ └────┬────┘ └────────┬────────┘ │
│ │ │ │ │
│ └─────────────┼──────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Agent Process │ │
│ │ │ │
│ │ • AgentSession │ │
│ │ • Event parser │ │
│ │ • Elicitation │ │
│ └───────┬────────┘ │
│ │ │
└─────────────────────┼───────────────────────────────┘
│ Flux HTTP API
│ run / resume / SSE stream
│
┌────────▼────────┐
│ Flux Server │
│ │
│ • Auth / RBAC │
│ • Agent defs │
│ • Config store │
│ • Executions │
└────────┬────────┘
│ dispatch
│
┌────────▼────────┐
│ Worker │
│ │
│ • agent_chat │
│ workflow │
│ • agent() loop │
│ • Tools / MCP │
│ • Memory │
└─────────────────┘
The agent process never runs the model itself. It is a stateless client: it starts or resumes an execution of the agents/agent_chat workflow on the server, streams the resulting SSE events, renders them for the user, prompts for the next message, and resumes. The worker does the model calls, tool execution, and memory work.
Concepts¶
Agent definition¶
A row in the agents table, created via flux agent create. Holds everything needed to instantiate the chat loop: model, system prompt, tools, MCP servers, memory, planning flags, and so on. See the YAML specification for the full field list.
Agents are global (not namespaced). The name is the primary key. Permissions use the format agent:<name>:<action>.
Configuration (configs table)¶
Plaintext key-value entries stored in the database. Analogous to secrets but without encryption — suitable for non-sensitive runtime values that change independently of code.
flux config set logging.level info
flux config get logging.level
flux config list
flux config remove logging.level
Agent definitions are themselves stored as configs under the key agent:<name>. The built-in agent_chat workflow reads its definition from this key at runtime via the generic get_config task.
Session¶
A session is a running execution of the agent_chat workflow. The session ID and the Flux execution ID are the same value. There is no separate session store.
- Start a new session:
flux agent start <name> - Resume a session:
flux agent start <name> --session <id>orflux agent session resume <id> - Sessions are not owned by a user; any principal with
agent:<name>:startandworkflow:agents:agent_chat:runcan resume.
Event stream¶
Sessions stream SSE events. The agent process parses raw Flux SSE frames into AgentEvent kinds consumed by any UI:
| Kind | Emitted when | Payload |
|---|---|---|
session_id |
Workflow starts | {"id": "<exec-id>"} |
token |
LLM streams tokens (stream mode only, outside tool loop in v1) | {"text": "..."} |
tool_start |
Before a tool call | {"name": "...", "args": {...}} |
tool_done |
After a tool call | {"name": "...", "status": "success" \| "error"} |
chat_response |
Workflow pauses waiting for user input | {"content": "...", "turn": N} |
elicitation |
MCP server requests out-of-band authorization | {"elicitation_id", "url", "message", "server_name", "mode"} |
session_end |
Workflow pauses with final session output | {"reason": "max_turns"\|"user_exit"\|"error", "turns": N} |
Unknown raw frames are silently dropped — the parser is forward-compatible.
Quick Start¶
Prereq: a running Flux server and worker. If auth is enabled, sign in first:
Define a minimal agent in YAML:
# assistant.yaml
name: assistant
model: anthropic/claude-sonnet-4-20250514
system_prompt: |
You are a helpful coding assistant. Keep answers concise.
description: General-purpose coding helper
Create it:
Start a terminal session:
You see a prompt. Type a message, press Enter. When you are done, press Ctrl+D or type /quit. The session ID is printed on exit so you can resume later:
CLI Reference¶
flux agent create¶
Create a new agent definition. Options can come from flags, a YAML --file, or a combination (flags win).
flux agent create <name> \
[--file agent.yaml] \
[--model provider/name] \
[--system-prompt TEXT | --system-prompt-file PATH] \
[--description TEXT] \
[--tools NAME]... \
[--tools-file PATH] \
[--workflow-file PATH] \
[--mcp-server URL]... \
[--skills-dir PATH] \
[--planning | --no-planning] \
[--max-tool-calls N] \
[--max-tokens N] \
[--reasoning-effort low|medium|high] \
[--format simple|json]
--tools is a repeatable flag that takes a built-in tool group name (system_tools, shell, files, search, directory). Individual tool names from the resolver are also accepted. --mcp-server is repeatable and records only the URL; richer MCP configuration (auth, secret reference, name) must go through --file.
flux agent list¶
flux agent show¶
Default format is yaml.
flux agent update¶
flux agent update <name> \
[--file agent.yaml] \
[--model ...] \
[--system-prompt ... | --system-prompt-file ...] \
[--description ...] \
[--planning | --no-planning] \
[--max-tool-calls N] \
[--reasoning-effort low|medium|high] \
[--format simple|json]
Fields not supplied are preserved. When both --file and flags are given, flags override.
flux agent delete¶
flux agent start¶
flux agent start <name> \
[--mode terminal|web|api] \
[--session SESSION_ID] \
[--port PORT] \
[--server URL]
--modedefaults toterminal.--sessionresumes an existing session instead of starting a new one.--portapplies only towebandapi; defaults to8080.--serveroverrides the Flux server URL; defaults tohttp://<server_host>:<server_port>from config.- The auth token is resolved from
$FLUX_AUTH_TOKENfirst, then from the OIDC credentials stored byflux auth login.
flux agent stop¶
Cancels the underlying Flux execution via POST /executions/<id>/cancel.
flux agent session¶
flux agent session list [<agent-name>] [--state <state>] [--limit <n>] [--format simple|json]
flux agent session show <session-id> [--format simple|json]
flux agent session resume <session-id>
resume is a shortcut for flux agent start <name> --mode terminal --session <id> that does not require the agent name — it attaches to the execution directly.
session list is backed by the /agents/sessions and /agents/{name}/sessions HTTP endpoints, which join the agent_sessions table against executions. Sessions are recorded automatically on first run for any workflow invoked under the agents namespace with an agent field in its input — this includes both the built-in agent_chat template and per-agent custom workflows. Sessions started before this feature landed do not appear in the listing; use flux execution list for those.
flux config¶
Key-value config used by agents and any task that declares config_requests.
flux config set <name> <value> [--format simple|json]
flux config get <name> [--format simple|json]
flux config list [--format simple|json]
flux config remove <name> [--format simple|json]
Values are stored in plaintext. Do not put secrets here — use flux secrets for that.
Agent YAML Specification¶
Every field maps 1:1 to a column in the agents table and to the AgentDefinition Pydantic model in flux.agents.types.
name: coder # required, primary key
model: anthropic/claude-sonnet-4-20250514 # required, provider/model
system_prompt: | # required
You are a coding assistant. Be concise.
description: A coding assistant with full system access
# Tools — see Tool Configuration below for details.
tools:
- system_tools:
workspace: /home/user/project
timeout: 60
max_output_chars: 200000
- shell:
workspace: /home/user/project
- file: ./custom_tools.py # Python file with @task functions
- read_file # cherry-pick by name
tools_file: ./custom_tools.py # alternative to the inline file: entry
workflow_file: ./custom_chat.py # escape hatch: custom workflow, see below
# MCP servers. Only `url` is required; the rest is optional and
# depends on the MCP client's auth handling.
mcp_servers:
- url: http://localhost:8080/mcp
name: github
auth: bearer
secret: GITHUB_TOKEN # resolved from Flux secrets at runtime
# Skills directory. Resolved at creation time; contents are stored inline.
skills_dir: ./skills
# Sub-agents this agent can delegate to. Must exist in the agents table.
agents:
- researcher
- reviewer
# Planning (structured multi-step)
planning: true
max_plan_steps: 20
approve_plan: false
# Tool loop limits
max_tool_calls: 20
max_concurrent_tools: 4
# LLM limits
max_tokens: 4096
stream: true
# Approval mode for tool execution (default | always | never — see Human-in-the-loop)
approval_mode: default
# Reasoning depth for models that support it
reasoning_effort: high
# Optional long-term memory
long_term_memory:
provider: sqlite
connection: "memory.db"
scope: "user:default"
Working memory is always on; there is no flag to disable it.
Field validation¶
modelmust be inprovider/model_nameformat (the/is required).reasoning_effortmust below,medium,high, or omitted.toolsis a free-form list of strings, dicts, orfile:references; validation happens in the tools resolver at runtime.
File resolution at creation time¶
skills_dir, tools_file, and workflow_file are paths on the machine running flux agent create. Because the workflow runs on a worker — potentially a different machine — these files are read and stored inline in the database at creation (and again on update if paths are supplied). The worker never reads them from the local filesystem.
Permission requirement. When auth is enabled, shipping any of these inline payloads requires the workflow:*:*:register permission, not just agent:*:create. The contents are materialized on every worker that runs the agent, so the gate matches the one for registering workflow source code. Plain path-string skills_dir (referencing an existing directory on the worker host, not an inline bundle) does not trigger the elevated gate.
Custom workflows¶
The built-in template is agents/agent_chat. Most agents do not need anything else. For advanced flows, supply workflow_file with a custom workflow that follows the same contract:
- Input on first run:
{"agent": "<name>"}. - Pause with
ChatResponseOutput,SessionEndOutput, or an elicitation payload. - Resume with
{"message": "..."}for user turns or{"elicitation_response": {...}}for elicitation.
Serving Modes¶
Terminal¶
- Direct readline interaction, no HTTP server.
- Tokens stream inline; tool calls print a
Calling name(args)... Done.line. - In-chat commands:
/help— list commands./session— print the current session ID./quit— exit cleanly.Ctrl+D(EOF) exits.Ctrl+Ccancels the current turn.- On exit, the session ID is printed so you can resume later:
- Elicitation is handled inline: the URL is printed, and you are prompted
Open browser to authorize? [Y/n]. Answering yes callswebbrowser.open(url).
Web¶
- Binds to
0.0.0.0:<port>and serves a single-page chat UI atGET /. - The SSE
/chatendpoint uses the operator's Flux token, set at process start time (either$FLUX_AUTH_TOKENor refreshed viaflux auth login). No per-request Bearer is required. - Intended for a single operator. If you expose web mode publicly, put it behind a reverse proxy that enforces your own access control — Flux does not authenticate the browser side of web mode.
- Elicitation renders as a banner message with a link that opens the authorization URL in a new tab; the UI resumes automatically when the user responds.
- The UI follows the system light/dark preference via CSS
prefers-color-scheme.
API¶
Headless SSE service. Every non-health endpoint requires a Bearer token that is passed through to the Flux server on a per-request basis — the operator token supplied at process start is not used.
| Method | Path | Body | Notes |
|---|---|---|---|
GET |
/health |
— | Public. Returns {"status": "ok"}. |
POST |
/chat |
{"message": "..."} |
Starts a new session. SSE response. First frames include a session_id event. |
POST |
/chat?session=<id> |
{"message": "..."} |
Resumes an existing session. SSE response. |
POST |
/elicitation/{elicitation_id}?session=<id> |
{"elicitation_id": "...", "action": "accept"\|"decline"\|"cancel"} |
Resume the session in response to an elicitation. SSE response. |
GET |
/session/{id} |
— | Proxy to GET /executions/{id} on the Flux server. |
SSE frames are JSON objects with a type field matching the event kinds above (except chat_response is serialized as type: response on the wire, for backward 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": "Hi! What can I help with?", "turn": 1}
{"type": "elicitation", "elicitation_id": "el-1", "url": "https://auth...", "message": "Authorize", "server_name": "github", "mode": "url"}
{"type": "session_end", "reason": "user_exit", "turns": 5}
{"type": "error", "message": "..."}
Example curl (note -N to disable buffering):
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"}'
Resume:
curl -N -X POST "http://localhost:8080/chat?session=$SESSION_ID" \
-H "Authorization: Bearer $FLUX_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "now summarize what you found"}'
Respond to an elicitation:
curl -N -X POST "http://localhost:8080/elicitation/el-1?session=$SESSION_ID" \
-H "Authorization: Bearer $FLUX_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"elicitation_id": "el-1", "action": "accept"}'
Tool Configuration¶
Tool entries are resolved by flux.agents.tools_resolver. Four forms are supported.
Bundled tool set¶
system_tools includes shell, files, search, and directory tools against a single workspace:
tools:
- system_tools:
workspace: /home/user/project
timeout: 60
max_output_chars: 200000
blocklist:
- rm -rf
- sudo
Omitted fields use the defaults: workspace=".", timeout=30, max_output_chars=100000, and the built-in DEFAULT_BLOCKLIST from flux.tasks.ai.tools.system_tools. See System Tools for the full behavior.
Individual groups¶
Select only the groups you need. Each takes the same config as system_tools:
tools:
- shell:
workspace: /home/user/project
- files:
workspace: /home/user/project
- search:
workspace: /home/user/project
- directory:
workspace: /home/user/project
Custom tools from a Python file¶
Point at a Python file whose top-level @task functions should be exposed as tools:
Or, equivalently, the top-level tools_file field:
The file is read at flux agent create time and stored in the agent definition; the worker uses the stored copy.
Cherry-pick by name¶
A plain string entry resolves via the tools resolver. In the current implementation the resolver accepts the bundled group names (system_tools, shell, files, search, directory); use file: for everything else.
MCP Integration¶
Add one or more MCP servers in the agent YAML:
mcp_servers:
- url: http://localhost:8080/mcp
name: flux
- url: https://mcp.github.example.com
name: github
auth: bearer
secret: GITHUB_TOKEN
At runtime the worker connects, discovers tools, and makes them available to the agent loop alongside local tools. See MCP Client for details on the underlying mcp() task, authentication modes, and tool discovery.
Elicitation (URL mode)¶
Some MCP servers require user authorization before exposing a tool. The MCP client advertises URL-mode elicitation during the handshake. When a tool call triggers an elicitation/create response (or a -32042 URLElicitationRequiredError), the worker pauses the workflow with a payload of:
{
"type": "elicitation",
"mode": "url",
"elicitation_id": "el-1",
"url": "https://auth.example.com/...",
"message": "Authorize Flux to access GitHub",
"server_name": "github"
}
The agent process observes the pause, handles the UX for the current mode, and resumes with an ElicitationResponse:
- Terminal: prints
[github] Authorize Flux ..., promptsOpen browser to authorize? [Y/n], and on yes callswebbrowser.open(url). Resumes withaction: acceptordecline. - Web: renders a clickable link (
Click to authorize) that opens in a new tab. The operator completes the flow on the provider's site; the page stays open and the agent process resumes when the user submits. - API: emits a
{"type": "elicitation", ...}SSE event. The client is responsible for directing the end-user to the URL and then callingPOST /elicitation/{id}?session=<id>with the chosen action.
Elicitation is handled in a generic way in the MCP client — any workflow that uses MCP tools can pause with this payload, not just agents.
Authentication and Authorization¶
The agent harness reuses Flux's existing RBAC. No separate auth system.
Token resolution¶
flux agent start resolves the operator token in this order:
FLUX_AUTH_TOKENenvironment variable.- Bearer token derived from OIDC credentials saved by
flux auth login(refreshed on each invocation).
If no token can be resolved, the process runs unauthenticated; this only works against servers with auth disabled.
Permissions¶
The harness adds two new namespaces on top of the workflow/execution namespaces:
agent:*:create
agent:*:read
agent:<name>:read
agent:<name>:update
agent:<name>:delete
agent:<name>:start
agent:<name>:session:read
config:*:read
config:*:manage
Because agent_chat is a real workflow, the workflow namespace still applies. Most agent actions require both an agent:* permission and the matching workflow:agents:agent_chat:* permission.
| Action | Required permissions |
|---|---|
| Create an agent definition | agent:*:create |
| List / show agents | agent:*:read or agent:<name>:read |
| Update / delete an agent | agent:<name>:update / agent:<name>:delete |
| Start or resume a session | agent:<name>:start + workflow:agents:agent_chat:run |
| Execute tasks inside the workflow | workflow:agents:agent_chat:task:<task>:execute |
| List sessions for an agent | agent:<name>:session:read + execution:*:read |
| Read config | config:*:read |
| Set / remove config | config:*:manage |
Built-in roles¶
| Role | Agent / config permissions |
|---|---|
admin |
All (inherited via *). |
operator |
agent:*:*, config:*:read, config:*:manage (plus the standard workflow/execution/schedule perms). |
viewer |
agent:*:read, config:*:read. |
See Authentication & Authorization for the general RBAC model, role-management CLI, and permission wildcard rules.
Deployment¶
The agent process is a thin client of the Flux server. It talks over HTTPS and SSE, so it can run anywhere the Flux API is reachable.
- Terminal: runs on the operator's machine. No network surface beyond its outbound HTTP calls.
- Web: binds to
0.0.0.0by default but is designed as a single-operator chat UI. Put a reverse proxy (nginx, Caddy) in front if you need to expose it beyond localhost, and enforce your own authentication there. Web mode does not check a per-request Bearer token. - API: multi-client by design. Each request must carry a Bearer token; tokens are passed through to the Flux server untrusted on the agent process side.
Pinning agents to specific workers¶
The agent_chat workflow dispatches like any other Flux workflow. If your agent needs a specific environment (browser automation, a desktop sandbox, a GPU), combine the harness with Worker Affinity:
# Custom workflow file referenced by workflow_file in the agent YAML
from flux import workflow, ExecutionContext
@workflow.with_options(namespace="agents", affinity={"role": "harness"})
async def agent_chat(ctx: ExecutionContext):
... # same contract as the built-in template
Pair that with workflow_file: ./custom_chat.py in the agent definition.
Scaling¶
Agent processes are stateless and can be horizontally scaled behind a load balancer when running in api mode. Sessions are addressed by execution ID, which is globally unique, so two processes can serve different sessions of the same agent concurrently without any shared state.
Examples¶
Ready-to-use agent definitions and supporting code are in examples/agents/. Each YAML file is a complete agent definition you can create and run immediately.
| Example | What it shows |
|---|---|
assistant.yaml |
Minimal agent — model + system prompt, no tools |
coder.yaml |
System tools, MCP integration, planning, reasoning |
ollama_local.yaml |
Fully offline agent using a local Ollama model |
researcher.yaml |
Long-term memory, skills, plan approval |
delegation.yaml |
Lead agent delegating to specialist sub-agents |
custom_tools.py |
@task functions used as agent tools via --tools-file |
custom_workflow.py |
Custom chat loop with welcome message and turn limit |
api_client.py |
Python client for headless API mode interaction |
Custom tools¶
Define @task functions in a Python file and reference them at creation time:
flux agent create support-bot \
--model anthropic/claude-sonnet-4-20250514 \
--system-prompt "You are a support agent." \
--tools-file examples/agents/custom_tools.py
Each @task function becomes a callable tool. The function name is the tool name and the docstring is the tool description sent to the LLM. At creation time, the file is read and stored inline in the agent definition — workers load it from the database, not the filesystem.
Custom workflows¶
Override the built-in agent_chat template with a workflow file:
flux agent create my-agent \
--model anthropic/claude-sonnet-4-20250514 \
--system-prompt "You are a helpful assistant." \
--workflow-file examples/agents/custom_workflow.py
Custom workflows must follow the agent workflow contract: accept {"agent": "<name>"} as input, pause with typed output, accept {"message": "..."} on resume.
API mode integration¶
Start an agent in headless API mode and interact from any HTTP client:
Endpoints:
POST /chat Start a new session (SSE response)
POST /chat?session=<id> Resume a session (SSE response)
POST /elicitation/<id> Respond to an MCP elicitation
GET /health Health check
Every request except /health requires Authorization: Bearer <token>. See examples/agents/api_client.py for a Python client example.
Example: A Coding Assistant¶
This walks through a realistic end-to-end flow: define an agent, start a session, have a conversation, resume it later.
1. Define the agent¶
# coder.yaml
name: coder
model: anthropic/claude-sonnet-4-20250514
description: Coding assistant with workspace access and GitHub MCP.
system_prompt: |
You are a senior engineer helping the user write and review code.
Use the available tools to read, search, and edit files in the project.
Always run the test suite after making changes. Be concise.
tools:
- system_tools:
workspace: /home/user/project
timeout: 60
max_output_chars: 200000
mcp_servers:
- url: https://mcp.github.example.com
name: github
auth: bearer
secret: GITHUB_TOKEN
planning: true
max_plan_steps: 15
max_tool_calls: 30
reasoning_effort: medium
2. Create it¶
flux secrets set GITHUB_TOKEN "ghp_..."
flux agent create coder --file coder.yaml
flux agent show coder
3. Start a terminal session¶
Flux Agent — coder
Session: 9e1b2f0c-4a2a-4e3d-b0f1-6a8a7a4c1b20
Type /help for commands, Ctrl+D to exit.
> find all TODO comments in the project and summarize them
Calling shell(cmd="rg -n TODO .")... Done.
Found 3 TODO comments:
1. src/parser.py:82 — "refactor this when we drop Py3.9"
...
> /quit
Session: 9e1b2f0c-4a2a-4e3d-b0f1-6a8a7a4c1b20
4. Resume later¶
5. Stop a runaway session¶
If something goes wrong mid-turn:
This calls the Flux execution cancel endpoint. The next resume will not reattach — the session is terminated.
Troubleshooting¶
Error starting agent: ... 401 — the process could not authenticate to the Flux server. Run flux auth login, or set FLUX_AUTH_TOKEN to a valid Bearer token. Confirm with flux auth status.
Missing or invalid Authorization header — you hit the API mode /chat endpoint without a Bearer token. API mode does not fall back to the operator token; every request must carry its own Authorization: Bearer ... header.
Agent process started without a token — web mode was started without a way to resolve the operator token. Run flux auth login first, or launch with FLUX_AUTH_TOKEN set in the environment.
MCP server prompts for authorization repeatedly — elicitation state is not cached across sessions in v1. If you decline or cancel, the next tool call on that server will pause again. Accept once and keep the session alive for the remainder of the work.
Session won't resume — the underlying execution may have ended or errored. Inspect it directly:
If it is COMPLETED, CANCELLED, or FAILED, start a new session instead.
Tools are never called — check approval_mode: always requires a human to approve every tool call, and never disables tools entirely. Also confirm the caller has workflow:agents:agent_chat:task:<task_name>:execute for the tools the agent uses — missing task permissions silently skip or reject the call depending on server configuration.
Unknown tool group — the tools resolver only recognizes system_tools, shell, files, search, and directory out of the box. For anything else, expose it as a @task in a Python file and reference it via file: or tools_file:.
Event stream ends with no chat_response — the workflow paused on something the parser does not recognize and the loop exits. Check the execution's task events via GET /executions/<id> on the server. Unknown pause outputs are ignored on the parser side to stay forward-compatible; future Flux versions may add new pause types.