Skip to content
archmax.ai

Workflows and the editor

A workflow is one state machine the archmax harness executes, authored as workflows/<slug>/workflow.yaml in an environment. The editor writes that file for you, and every change lands in the environment’s draft until you publish it (see Agents and environments). This page covers what the file holds and how the editor maps onto it. For the full semantics of the machine, read the harness’s workflow machine guide and its machine spec reference.

Each workflow is a directory under workflows/:

  • workflow.yaml is the machine spec, a plain YAML mapping and the only source of the machine;
  • WORKFLOW.md beside it is optional prose with no frontmatter, which the agent reads after the root instructions (HTML comments stripped);
  • hooks/ holds the workflow’s lifecycle hook scripts;
  • tests/ holds its cases (see Evaluations).

The directory name is the workflow’s identifier: lowercase letters and digits in segments joined by hyphens, such as order-intake. It is proposed from the title when you create the workflow and cannot change afterwards, because chat conversations, evaluations and trigger deployments all address the workflow by it. The title is the label, and renaming means changing the title.

The schema is the harness’s own, and it is strict: a key it does not declare, misspelled or at the wrong level, is refused with an error naming it, and nothing strips or rewrites it. The platform adds only what the runtime never reads: canvas positions, colours and icons in metadata slots, and its own keys inside trigger declarations (see Triggers).

title: Refund review
instructions: |-
Answer only from what the customer and your tools have told you. Never invent an
order, a status or an amount.
runtime:
engine: archmax-harness
version: "2"
tools:
forbid_always:
- archmax_reset
skills:
allow_always: []
states:
intake:
title: Assess the request
triggers:
manual:
requires:
- order_id
instructions: >-
Decide whether a refund applies to the order named by order_id, write the
decision to scratchpad/refund.json, and then advance to review.
after:
rubric:
instructions: >-
Return ok when the last reply names the order id and the decision,
correct when it leaves either out, and veto when it promises a refund
the decision does not grant. Always give a short reason.
max_iterations: 2
transitions:
- to: review
description: The decision is written to scratchpad/refund.json.
review:
title: Review the decision
type: human
approvers:
- finance@example.com
evidence:
- scratchpad/refund.json
instructions: A person checks the recorded decision before the customer is told.
transitions:
- to: close
type: approve
description: The decision is correct; tell the customer.
- to: intake
type: refine
description: The decision needs another look.
close:
title: Tell the customer
instructions: >-
Confirm the reviewed decision to the customer with the order id and the
reason. This state is terminal.

The root instructions are standing direction for the whole session. intake is the start state: its manual trigger lets it be started by hand or called by another workflow, and a start must supply order_id. Its after rubric grades the agent’s work before the transition to review commits. review is a human state: the session parks there until a person picks one of its two transitions. close has no transitions, so it is terminal. No state may call archmax_reset, and the explicit empty skills list says the workflow grants no skill workflow-wide.

Each key under states is that state’s slug, its only identity: transitions, on_error routes, trigger bindings, session history and cases all refer to it. A state’s title is presentational and may repeat. instructions are what the agent follows while the state is active.

A state’s type is agent (the default) or human. Waiting and delegating are not kinds of state: an agent state parks itself by calling archmax_wait, and runs another workflow by calling that workflow’s archmax_workflow_<slug> tool, which it must be allowed (see Sessions and decisions). The workflow it runs works in a folder of its own: hand it a file through a type: file entry of its manual trigger, and take one back through a type: file entry of its returns.

A transition is { to, description, type? }. The description is required and is all the agent is told about the transition, so write when to take it, not where it leads. On a human state’s transitions, type (approve, reject, refine, none) gives the decision dialog its accent. A state with no transitions is terminal. Keys the editor does not author, such as a state’s budget, on_error and requires, are kept exactly as written.

A state may declare before and after hooks, each a single hook or an ordered list that stops at the first verdict other than ok. A before hook decides entry and returns ok or veto. An after hook decides the transition out of the state, or the completion of a terminal one, and may also return correct, which sends the agent back with the reason for another attempt.

  • A script hook names a file in the workflow’s own hooks/ directory, workflow-relative: before: { script: hooks/check-requester.js }. A path into a skill bundle, an absolute path or one with .. is refused, and a missing file is reported when you save. A script hook reads only the mounts its own workflow grants, in mounts.allow_always or in any state’s mounts.allow, and writes one only where a grant asks for read_write. The workflow’s mounts.forbid_always and skills.forbid_always bind it too; a state’s own allow, forbid and access: read do not.
  • A rubric hook carries its grading criteria inline as instructions, with an optional model to grade on and, on an after hook only, max_iterations: how many correct verdicts may send the agent back before the next one blocks. A rubric has no name and is not shared; a second state that needs the same standard states it again.

The agent can read neither: workflows/ is not part of any agent’s workspace. See the harness’s grading rubrics guide.

What a state’s agent may use is governed by allow and forbid lists at two levels:

Level Tools Skills Mounts
the workflow, every state tools.allow_always, tools.forbid_always skills.allow_always, skills.forbid_always mounts.allow_always, mounts.forbid_always
one state tools.allow, tools.forbid skills.allow, skills.forbid mounts.allow, mounts.forbid

A state gets what the workflow grants everywhere plus its own grants, minus every forbid that applies. A forbid beats a grant, and no narrower level widens a denial.

  • A tool entry is a tool id, or a mapping { tool, args?, paths?, connection?, source? }. A piece action’s id is <collection>__<action> with source: ap, an MCP tool’s carries source: mcp, and a platform tool needs no source. connection binds the entry to one connection; args holds argument guards, glob patterns a parameter’s value must match. In the editor, a guard on a parameter whose options come from the connection is a checklist of them. A value the list does not hold is typed into its search and added with Use this value. Focusing a free-text guard offers suggestions before the values the parameter recently received:

    • the patterns the tool itself recommends;
    • any variable the recent calls’ values were. If the last five calls passed five different ids, all of them triggers.-1.session, the editor offers ${{triggers.-1.session}} instead of the ids.

    A tool may also show a parameter’s guard row from the start, with its recommendation as the placeholder. The built-in file and runtime tools are on in every state, so they are only ever forbidden. On the forbid side alone, tool: "*" with paths takes a set of paths away from every tool.

  • The platform’s own tools need no connection and are granted like any other: Files (files__download_file, files__create_presigned_url, files__unpack_file, files__pack_files), REST (rest__request) and Sessions. The Sessions tools are:

    • sessions__set_attributes, which records session attributes on the session or on the session a completion trigger opened it about, named by id (${{session}} or ${{triggers.-1.session}});
    • sessions__read_subject, which reads that session’s transcript.
  • A skill is granted by its slug. Granting it makes its whole bundle readable and its scripts runnable in that state with no path entries; a skill a state is not granted is never even named to its agent. See Skills and registries.

  • A mount is a folder of the environment’s Storage or a remote mount, read-only unless the entry asks for read_write. See Files and mounts.

disabled: true at the root means the workflow starts no new turns: triggers start nothing and it cannot be started by hand. Turns already underway finish, and parked sessions can still be decided, replied to and delivered into. Its trigger deployments stay active, so re-enabling it needs no reconfiguration. The editor’s Disable workflow checkbox writes the key and removes it again.

Opening a workflow from the Workflows page shows its workspace: a header with the workflow’s name and the Editor and Evaluations views. Each view has an editor mode and a session mode over the same graph; the mode bubble at the top of the canvas switches between them (Show sessions, Back to editor) and carries the detail panel’s toggle, which in the editor’s own mode is Workflow settings. Every location, down to the selected session or case, is in the URL. The session mode is described in Sessions and decisions.

States are cards and transitions are arrows. Add state at the bottom of the canvas adds an agent state; drawing from one card’s handle to another adds a transition, and releasing on empty canvas creates a new state already wired to it. Cards snap to a 16 px grid.

A card shows the state’s title (or its slug), a human badge on a human state, and up to three count groups: the triggers that enter it, its tools, and its sub-workflows. Hovering a group lists its members; clicking it opens the inspector at the matching section. A group whose member cannot be resolved, such as a tool whose collection has no connection, leads with a warning glyph. Lock marks on a card’s left and right sides stand for its before and after hooks. A start state carries a ▶ to its left, which starts a session (see Triggers).

Selecting a state opens the inspector beside the canvas: Title and Identifier first, then Kind (Agent or Human), Instructions, and sections for the Trigger, the before and after hooks, Tools, Sub-workflows, Skills, File mounts (on an agent state) and Appearance. Changing an identifier rewrites every reference to it in the workflow; a new state’s generated identifier follows its title until you set one yourself. A human state also edits its approvers, the people the decision dialog names. Selecting a transition edits its description and appearance.

Every list adds through a plus in its heading. Tools and triggers share one picker: collections on the left (the platform first, then connected ones, then the rest), their items on the right, any number ticked and confirmed at once. Tools and skills each have a grant control and a forbid control side by side, and a grant that a forbid cancels is shown as denied, naming the list that denies it.

Workflow settings opens the workflow’s own surface in the inspector: its title, its identifier (read-only), its standing instructions, Workflow tools and Workflow skills (the allow_always and forbid_always lists), File mounts, Disable workflow and the workflow’s spend caps in the open environment (see Spend and spend caps).

The editor saves by itself: shortly after you stop editing, or after a bounded wait while you keep going, and at once with Cmd/Ctrl+S. The header shows Saved, Unsaved changes, Saving…, or Couldn’t save with Retry. Starting a session from the ▶ saves any pending edit first.

Validation reports with the save rather than instead of it: a transition to a missing state, a missing hook script or an unknown skill is written to the draft and named in the validation banner, and warnings appear beside the field they concern. The one thing refused outright is a document that does not parse as a machine spec. A missing connection is only ever a warning, so a workflow can be designed first and connected later.

New workflow on the Workflows page asks for a title, an identifier and optional standing instructions. A workflow can also start from a template: the platform’s built-in ones, and any the environment holds under .archmax-templates/<id>/. The import control there creates a workflow from a .yaml or .yml file, and the editor’s download control saves what is on the canvas as a workflow.yaml document, unsaved edits included. A workflow is deleted from its row on the Workflows page, which removes its directory from the draft as a pending change.