Skip to main content

Development node exclusions

An exclusion keeps a Project off one app-dev Node for development. One row records one Project and one Node. The Project commands and the Node commands read and write the same row. With no rows, a Project can use every eligible app-dev Node. The task workspace routing setting changes whether a new workspace gets a Route; it does not change placement or exclusions.

Record an exclusion

The Node must have an active app-dev role. The Node argument is an ID or a name. The Project argument is a numeric Project ID.
Both commands store the same row. A second add of the same pair returns the stored row. In a terminal, an omitted Project or Node opens a searchable selector. JSON and noninteractive calls must name both for add and remove, and the owner for list. remove asks for no confirmation, because it moves and stops nothing. project:show includes excluded_nodes, and node:show includes excluded_projects. Each entry has the Project ID and slug, the Node ID and name, and development_instance_count: the number of development Instances of the Project already on the Node. Those Instances stay where they are. Use transfer to move one. Removing the app-dev role from a Node deletes its rows. Adding the role again does not restore them. Deleting the Node or the Project deletes its rows.

Where Orbit applies the list

Orbit checks the list for every new development placement:
  • The task scheduler skips excluded Nodes.
  • Among the other Nodes, it picks the one with the fewest active tasks.
  • When no Node remains, the task waits without a workspace.
  • instance:create, instance:register, and instance:transfer to an excluded Node return instance.node_excluded before they change anything.
Production placement ignores the list. A Node that also has app-prod can still host a production Instance of the Project.

MCP

The operations are Gateway API routes, so the MCP server offers them as tools with the same validation and errors. A tool takes the route’s path parameters as arguments. project is the numeric Project ID, and node is the numeric Node ID.

Errors

The Gateway returns these codes for exclusions and for refused placement.

Why it works this way

These reasons explain the design. Check them before you propose a change.

An exclusion list, not an allowlist

Most Projects can use every app-dev Node, and a new Node should get work at once. An empty list keeps that default. An allowlist was rejected because every new Node would need an entry for every Project. A Node capability flag, such as “runs Incus”, was rejected because Orbit does not record capabilities and the operator picks specific Nodes.

Two command families, one row

An operator starts from a Project or from a Node, and both see the same pairs. One family with a flag for the starting side was rejected.

Existing Instances stay

The list applies to new placement only. Moving Instances when a row is added was rejected. The add response shows how many Instances are affected, and the operator decides.

The row leaves with the role

Only app-dev Nodes take development work, so a row without the role means nothing. Adding the role again makes the Node eligible for every Project until someone excludes it again.