Skip to content

Working with an assistant

An AI assistant — Claude, Copilot in VS Code, Cursor — can work in XPressRT: list the steps and test cases, write new ones, create data points, read a run and explain what went wrong. It does this through the XPressRT window you have open, over the Model Context Protocol (MCP), and not beside it.

That is the whole design, and it is what makes it safe to use. The assistant can do what you can do in that window, in the workspace that is open there, and nothing else. What it creates appears in the interface at once. The assistant has its own page, Assistant in the sidebar (marked beta): that is where you connect it, and where What the assistant did lists its latest requests, newest first. The question mark beside XPressRT at the top of the sidebar leads there too: If you need help with anything, ask the agent.

The page is marked beta because the way works but still moves: which tools there are, what their descriptions say, how a change is marked.

What an assistant can and cannot do

It can It cannot
list procedures, test cases, steps, data points, suites, plans and runs change a result — a record that can be edited afterwards is no record
read a test case and change it reach a workspace other than the one open in the window
create procedures, test cases, steps and data points — an operator question or a reading with a picture, too start a real run: a run it starts is always a dry run
set the preparation and clean-up steps of a procedure approve a step built from blocks — there is no tool for it, and an approval is a person's signature
write the body of a step as blocks, and build them
check a procedure for what will not run
explain why a run failed
try a test out — start a dry run and answer its prompts itself

While an assistant is connected, the header shows a pink Assistant badge. It reads Assistant working while a request is being handled.

The rules it is given

Every workspace carries its house rules for assistants in AGENTS.md, written by XPressRT and renewed when they change. At the top, before anything about files and benches:

  • The chain. Test cases, steps, data points, benches: each layer knows only the next one, by its name, and none changes because another one is implemented differently. To automate a test case, the assistant changes what its existing steps do (change_step_action) — it does not write new sentences or change the test case. A sentence belongs to every test case that uses it.
  • Without a bench. A workspace is always runnable. Data points and steps come from what the test needs, not from what a piece of equipment offers; until a bench binds a point, the operator serves it — in a dry run the assistant does. A bench comes last, and binding it changes no test case.
  • Every step goes through data points. A check reads a point and XPressRT judges; a bare yes/no question to the operator ("Is this expected result true?") records nothing and is not what an assistant creates.
  • Runs. The assistant may start a dry run and answer its prompts; a real run is started by a person at the bench.

The same sentences open the assistant's skills for onboarding, coverage and regression.

Steps an assistant cannot create

The rule on data points is not only written down; create_step, change_step_action and import_plan hold to it. What decides is the shape of the step — its kind and what it does —, never its words:

Kind Refused for an assistant Allowed
Check every operator question, a wait, a serial number, a set step read, request, Python
Setup, Action an operator question with Yes / No everything else — an instruction the operator confirms, a typed answer, set, request, read, a serial number, a wait, Python

A refused call says which step it was and what to write instead, and nothing is written; import_plan names every such step of the plan at once. A person in the step form is not bound by this. Steps an assistant created before are not refused but counted: check_procedure and about list them, and change_step_action switches each one on the sentence it has — the test cases stay as they are.

Preparation and clean-up

Steps that belong to no single test case — once before the procedure, once after it, the Preparation steps before every test case, and before or after one test case — the assistant sets with set_preparation. The same rules hold as on the procedure page, and a few more, because these steps run outside the variants table:

  • what it passes replaces that place; an empty list clears it
  • every step has to be an existing sentence under its kind, with the value written in — no <column> and no {braces}
  • a step that does not resolve, or a test case that does not exist, is refused, and nothing is changed

A picture with an operator question

create_step and change_step_action take picture for a step of kind ask or read: the path of a PNG, JPG, GIF or WEBP file on the machine the assistant works on, up to 5 MB. It is put into the workspace the way the step form puts it there, and the operator sees it in the same dialog. Left out, the picture the step has stays; an empty string removes it, file and all. read_step names the picture a step shows.

Checking a procedure

check_procedure finds what will not run before anybody is standing at the bench: a sentence no step is defined for, a line written as the wrong kind, braces left in a test case or in the text the operator reads, a column the table does not have or no step uses, a data point that was deleted, a data point a step uses the wrong way round — reading one that is set, or setting one that is read — and the warnings on the blocks of the steps it uses. The preparation and clean-up steps are checked like any other.

It matters because none of these faults announce themselves in a run: a step that is not found does nothing and does not fail, so a test case carrying one comes back green with nothing checked. What it cannot check is judgement — whether a limit is right.

Writing a step body

The body of a Python step is written as blocks, not as Python: the assistant writes a file in local/blocks/, then calls build_blocks, which builds the Python under local/steps/ and names every line that did not build. If no block does what a step needs, it says which block is missing. See Blocks.

When the assistant is running a test

An assistant can start a dry run to find out whether what it wrote actually runs and whether its checks fire. It decides when it starts the run who answers the prompts: itself, or a person at the bench, in the ordinary dialog. A real run — the one that is evidence about a unit — is started by a person.

While a dry run goes, a window sits in front of the page saying so, with a Cancel the run button. That window is also the lock: nobody can start a second run while it stands, because two runs in one workspace write into the same place.

When the prompts go to the assistant, the run stands until it answers — which looks like a hang and is not; the window says what is being waited for. Two things you can do from where you are sitting:

  • Cancel it. Nothing is sent to the assistant at that moment. It finds out the next time it asks what the run is waiting for: the answer then says plainly that the run was stopped before it finished, that no further question will come, and that what came after the stopping point did not run.
  • Answer one yourself. The assistant can hand a single prompt over — a question that needs eyes on the unit, say — and then the ordinary dialog appears, with its picture, its readings and its forms. The prompts after it go back to the assistant.

A step that reads a record or a list asks for it as a whole: pending_question names the type and its fields, and the assistant answers with an object, a field with named values by its name. A check that collects what a point received over time is answered with one entry per occurrence and its moment — and for a point that reports events an empty list is an answer: nothing was received. Every field is checked against its type; what does not fit is refused with the field, what was expected and what came, and the question stays open. Such a question can be handed to a person like any other: they get the dialog with one input per field, or the table of occurrences.

create_step writes form steps on such points too. A read step says per field what it checks — "fields": {"code": {"min": 4000, "max": 5000}, "detail:severity": {"expect": "{sev}"}} — or, on a list point, the entry it has to contain ("contains"); a set step gives the record as target, a request a record parameter as an object. A parameter of the sentence stands wherever a single value stands, never inside a list. read_step says it back field by field.

A dry run is a run like any other: same record, same report — except that it never counts towards a test plan. What it proves is that the test runs, not anything about the unit — and where the assistant supplied the values, the record says so:

  • in series.json, each value it typed carries the origin assistant
  • in measurements.json, its readings carry the source assistant
  • in step-results.json, a value typed into a step carries enteredBy
  • a serial number it entered shows as set by the assistant in the report and when the assistant reads the run back with explain_run

What an assistant changes is marked

A procedure, test case, step or plan an assistant creates or changes carries the tag ai-generated afterwards. Every change it makes sets the tag again. Taking it off is the review: a person has read what the assistant wrote.

Marking is a second write after the change itself, and it can fail — most often when the page cannot load a part of itself any more. Then:

  • the change still applies
  • the tool's answer to the assistant carries a NOTE saying what was written but not marked
  • .xpress/unmarked-changes.json keeps each open entry, with what it touched, so it survives a reload (a workspace that still has the file at its root gets it moved there once, when it is opened)
  • the next time marking works, XPressRT marks the open entries one by one and removes each that it could mark; an entry that was too big to keep with its change stays in the file and has to be marked by hand
  • about lists what is still open

This is not the same as a request that did nothing. If the assistant reports that the XPressRT page could not load a part of itself and asks you to reload the page, nothing was done: reload the page, and let it try again.

The tools

The assistant sees these tools. You do not call them yourself, but knowing them tells you what to ask for.

For Tools
Finding your way about (versions, and what is still unmarked), describe_bench (who answers a reading, and what nobody here can know)
Procedures list_test_cases, create_procedure, update_procedure, copy_procedure, move_procedure, delete_procedure, check_procedure, set_preparation
Test cases read_test_case, read_test_cases (several at once), create_test_case, update_test_case, move_test_case, delete_test_case
Steps list_steps, read_step, similar_steps, create_step, change_step_action, change_step_kind, describe_step, rename_step, move_step, delete_step
Blocks list_blocks, build_blocks
Data points list_data_points, create_data_point, update_data_point, rename_data_point, rename_data_point_value, delete_data_point
Record types list_record_types, create_record_type, update_record_type, rename_record_type, rename_record_type_field, rename_record_type_value, delete_record_type — messages and bundles, see Records and lists. Renaming a field or one of its named values rewrites the block files and instructions that name it in one write — and, for a value, the test cases whose sentence or variants table gives it to that field; what_uses with record_type and field lists all of them beforehand, and Python written by hand is reported, not rewritten. check_procedure names a test case that gives a field a value it does not know.
Folders create_folder, rename_folder, delete_folder
Benches list_benches, read_binding, write_binding_row, remove_binding_row, propose_binding — rows of a binding only; commissioning stays with a person, see What an assistant does on a bench
Tags and suites list_tags, set_tag_colour, list_suites, create_suite, update_suite, delete_suite, set_suite_status
Plans list_plans, create_plan, update_plan, delete_plan, start_plan_cycle, close_plan_cycle, plan_progress
Dry runs start_dry_run, pending_question, answer_question, answer_questions (several from one table), hand_to_operator, stop_run
Reading runs list_runs, explain_run, history
What reaches what what_uses, what_uses_many, changed_since
Agent runs save_agent_run, validate_agent_run (the same checks, nothing written), read_agent_run, list_agent_runs, import_plan (a whole set at once), confirm_decomposition
Requests from a page open_requests, close_request, adapt_step
Sources read_sources, propose_source, withdraw_source
How to work here skill

Six prompts come with it. An assistant client shows them as commands you pick, not as something the assistant calls:

  • what_can_you_do — looks at what is in the workspace and says what could be done with it
  • explain_the_last_run — reads the newest run and explains what failed and why
  • build_a_procedure — walks from what is to be tested to the data points, the steps and the test cases, and shows you each part before writing it
  • bring_in_my_tests — reads the tests you already have and writes them here, with where each one came from; see Onboarding
  • show_me_coverage — works out what your test cases cover against the requirements; see Requirements
  • plan_a_regression — works out what a change affects; see Intelligent Regression

Some answers carry fields as well as words. pending_question, read_test_case, read_test_cases, list_test_cases and read_agent_run answer with the same thing twice: the text a person reads, and the fields an assistant would otherwise have to read back out of that text. It matters for one thing you might notice: an assistant that answers a dry run no longer has to guess what a dialog accepts, so fewer runs fail on a "yes" where the step wanted the word "confirm".

A dry run says how far it has got. While it runs, pending_question also answers with how many test case runs are done of how many, which test case it is in and which step it last reported. A row of a variants table counts as a run of its own, because that is how it is executed — a procedure with three rows is three runs, not one. The total is read from your file before the run starts; if it cannot be read, the answer says how much is done and not how much is left, rather than naming a number nobody counted.

A run can be checked before it is handed in. validate_agent_run runs exactly the checks save_agent_run runs and writes nothing: the answer says whether it would be taken and, if not, every reason. It is the same checks and not a second set, so a run this calls clean is one that goes in. What it cannot say is what comes about when a coverage run is laid onto the state — which decision stands, what comes back open. That happens there.

A wrong source can be taken back out. withdraw_source removes one on your word, with your name — a wrong address, a container nobody meant, one that was named twice. What was already handed in stays as it was: a run names the source it read, and that record is what it was at the time.

A request you leave on one of those pages — start coverage for these requirements, carry out this adaptation, write the missing test case — waits until an assistant calls XPressRT next. It is named above every answer the assistant gets, and open_requests has the whole of it. The page cannot reach the assistant; the line runs the other way.

The Agent queue

Before you tell your assistant Work through the open requests in XPressRT, you can see everything it will work through: the Agent queue, on the Assistant page under What it cannot do. The sidebar entry Assistant carries the count — Assistant · 3 — so you see that something is waiting.

It lists every open request from every page — an approved proposal or adaptation, a data point to create, a question for a proposal, a test case to write, a coverage run or a regression you scheduled. A kind of request that is new to this page still shows, under its own name. Each row says:

  • its number, in the order your assistant takes them — oldest first. The numbers are the ones open_requests gives: 1 of 5 in the conversation is row 1 here;
  • what kind of work it is, and a link to where it came from — the checkpoint, the data point, the regression run, the test case;
  • what exactly is to be done — the line your assistant gets, such as add "Check …" after step 2;
  • who asked and when, and the note they gave with it;
  • a warning when it no longer fits as things are now: the step is already in the test case, the test case changed or is gone, the data point exists. Your assistant sees the same warning before it starts;
  • the agent is working on this while your assistant makes calls under it.

Every row can be taken back, with a reason: an approval with Withdraw approval — what it approved stands to be decided again — and anything else with Withdraw. Both are kept under your name and show in the History. Schedule coverage, Schedule regression and Schedule onboarding — on the Requirements, Regression and Onboarding pages — each leave a request here; a scheduled onboarding names the source of the test cases, and only an onboarding run that read from that source closes it. A request withdrawn while the assistant was on it is not counted as done when it closes it; the assistant is told so.

The list follows what happens without a reload — also in another tab: a request the assistant closes disappears at once. Recently done, folded away, shows what was closed in the last seven days, with the assistant's answer.

The queue only shows and withdraws. The order is the one requests were made in; it cannot be changed here.

Connecting — workspace in a folder

Nothing to set up. Every workspace carries its own server, local/xpress_mcp.py, and the entries that tell Claude Code (.mcp.json) and VS Code (.vscode/mcp.json) to start it. XPressRT writes them, and keeps them current, whenever the workspace is opened.

  1. Open the workspace folder in Claude Code or VS Code. The assistant starts the server by itself; Claude Code asks once whether it may.
  2. In XPressRT, with the same workspace open, go to Assistant in the sidebar. Settings has an Open Assistant button that leads to the same page. A line says a server is waiting for this workspace. Press Connect.

That is the only time you press it. From then on the window connects on its own whenever the server is there, until you press Disconnect.

What it needs is Python 3.9 or newer — the same as a Python step. The server uses Python's standard library only, so there is nothing to install, and an update of XPressRT brings the new server along the next time the workspace is opened.

Why there is still a secret

The server listens on a port on your computer, and any web page in any tab could reach that port. So it leaves its address and a secret in .xpress/mcp.json inside the workspace, and the window reads them from there. A web page cannot read a file in your workspace folder — and nobody has to type anything.

Claude Desktop

Claude Desktop does not open folders, so it needs one entry — in Settings › Developer › Edit Config, with the path to your workspace:

{
  "mcpServers": {
    "xpressrt": {
      "command": "python3",
      "args": ["/path/to/your/workspace/local/xpress_mcp.py"]
    }
  }
}

On Windows, write python instead of python3.

More than one assistant

Several assistants can work in the same workspace at once — Claude Code and VS Code side by side, for example. Each starts its own copy of the server; the first one holds the connection to the window, and every later one asks the window through it. There is nothing to set up for this.

More than one workspace

Each workspace has its own server, and the server belongs to the folder it sits in. Open two workspaces in two assistants, and each talks to the XPressRT window that has its workspace open.

A window with a folder open connects only to the server whose handshake lies in that folder — not to whichever it was connected to before. Switch the window to another folder and it connects by itself to the server waiting there; if none waits yet, the Assistant page says so, names the folder the last server works for, and the window connects as soon as one starts. A server whose handshake was copied along with a folder does not take the copy over: it starts its own.

The window in the background

The assistant works through the XPressRT window, so the window has to be open — but it does not have to be in front. You work in your editor or with the assistant, and the window sits in a tab behind it.

A browser slows a background tab down: its timers fire once a second, and it gets less of the machine. XPressRT does not wait on those timers where something waits for the answer, and it reads what the recorded runs hold only once per run rather than for every data point — so a change through the assistant takes about as long in the background as in front.

Should a call still run out of time, the answer says whether the window was in the background — The XPressRT window is in the background, and the browser slows a background tab down — bring it to the front — and that nobody knows whether the change was carried out. The assistant then reads the state before it repeats anything. A window of its own beside the editor, rather than a tab behind other tabs, is never slowed.

A tool that changes something answers when its change is in the workspace history, not before: what the assistant did is recorded under its name by the time it says so.

How long a call may take

A call waits up to five minutes for a window that is there and working. Bringing in or handing in a whole run at once — import_plan, save_agent_run, validate_agent_run — may take up to an hour: an import of hundreds of test cases takes time, and nobody has to sit beside it. While such a call works, XPressRT tells the assistant's program every 15 seconds that it is still working, so the program does not give up on it. An answer that took longer than 20 seconds says how long, and whether the window was in the background. A window that goes away — reloaded, closed — is still reported after about 20 seconds, not after the deadline.

An assistant that repeats itself

An assistant that calls the same tool with the same arguments three times in a row, and gets the same answer each time, is stopped: the answer will not change, and it is told to ask you instead. A change asked for the same way a third time is not carried out. Asking again for something that changes with time — an open question of a run, the Agent queue, the list of runs — is waiting, not repeating, and is never stopped. The same holds for a change refused because a run is going: asked again once the run is over, it is carried out.

When the assistant cannot do something

A change that is refused leaves nothing behind: what the request had written up to that point is undone, the workspace is as it was, and the history has no entry for it. The answer says why. check_procedure and about name a step file that does not compile, with its file and line — such a file stops every run in the workspace.

Connecting — workspace in the browser

Not possible yet. An assistant needs the workspace as a folder on your computer: it opens the folder and starts its server there, and a workspace kept inside the browser has no folder. A download for such workspaces is planned. Until then, keep a workspace you want to work on with an assistant in a folder.

An assistant with access to the folder

Some assistants — Claude Code, for one — can also read and write files directly. Pointed at a workspace folder, such an assistant could change everything in it: the procedures, the generated definitions, the results of finished runs. XPressRT cannot take that access away. What it does is say where the line is, in a file every such assistant reads first: AGENTS.md at the root of the workspace (and CLAUDE.md, which points to it).

The rule it states: local/ is the assistant's, everything else belongs to the interface — and even in local/ a few files are XPressRT's.

  • In local/ go the data, the images and the helper modules the steps need. Nobody overwrites them.
  • A step body is not one of them. It is written as blocks, in local/blocks/<name>.blocks, and built with build_blocks. If no block fits, the assistant says which block is missing — never Python beside the blocks.
  • XPressRT's files in local/ are not to be written: the runner library, the MCP server and the READMEs, which are replaced on every open; the Python built from a block file; and local/blocks/approvals.json, which records who approved which step.

Everything else — procedures, preparation and clean-up included — goes through the tools above. A result is read, never written.

Both files are written and kept current by XPressRT whenever the workspace is opened. Do not edit them — your change would be replaced.