Spaces
A Space is a live selection over the Step catalog, and planning inside it considers only the Steps it selects.
Scoped Planning
A Space selects a coherent subset of the catalog from stable Step metadata. The same Goal can use different upstream networks in different Spaces: European fulfillment can select its regulatory and logistics Steps, while US fulfillment selects its own tax, carrier, and compliance Steps.
A Space carries an ID, a display name, an optional description, and an authoritative script selector. It may also retain the query-by-example (QBE) data used to generate that script:
{
"id": "risk",
"name": "Risk",
"description": "Steps owned by the risk domain",
"qbe": [["domain:risk", "environment:production"]],
"selector": {
"language": "jpath",
"script": "$.tags[?@==\"domain:risk\"] &&
$.tags[?@==\"environment:production\"]"
}
}
Tag Steps for Membership
A Step declares tags alongside its Attributes. Tags are a set of opaque strings, and a prefix:value shape is a convention you may adopt; the engine compares whole strings:
{
"id": "score-customer",
"name": "Score Customer",
"type": "service",
"tags": ["domain:payments", "domain:risk", "example", "tier:gold"],
"attributes": {
"customer_id": {"role": "required", "type": "string"},
"risk_score": {"role": "output", "type": "number"}
},
"http": {
"invoke": {
"endpoint": "https://risk.example.com/scores"
}
}
}
A Step carries as many tags as it needs, so score-customer belongs to both the risk and payments domains. QBE matches tags only: every tag within a set is required, and matching any set joins the Space. The example requires both domain:risk and environment:production.
List more sets to express alternatives. This Space holds the gold tier of two domains at once:
{
"id": "gold",
"name": "Gold",
"qbe": [
["domain:payments", "tier:gold"],
["domain:risk", "tier:gold"]
],
"selector": {
"language": "jpath",
"script": "($.tags[?@==\"domain:payments\"] &&
$.tags[?@==\"tier:gold\"]) ||
($.tags[?@==\"domain:risk\"] &&
$.tags[?@==\"tier:gold\"])"
}
}
Argyll stores each set sorted and deduplicated, stores the sets themselves sorted and deduplicated, and regenerates the JPath selector whenever the QBE changes.
Script Selectors
A selector is a Predicate over stable Step metadata, written in any registered scripting language. Lua receives the metadata document as value, along with the helpers has(list, value) and has_prefix(list, prefix) for testing the tag array, so a tag test reads has(value.tags, "domain:risk"). JPath receives the document directly, where the same test reads $.tags[?@=="domain:risk"]. For the Step above, the document is:
{
"tags": ["domain:payments", "domain:risk", "example", "tier:gold"],
"type": "service",
"handling": "standard",
"attributes": {
"customer_id": {
"role": "required",
"type": "string",
"compensated": false
},
"risk_score": {
"role": "output",
"type": "number",
"compensated": false
}
}
}
handling includes the standard default. Each member of attributes contains only role, type, and compensated; other Step fields and Attribute configuration are unavailable.
Tags can identify the business partition while the selector defines how unqualified Steps participate in it. The following selector includes Steps explicitly intended for Europe, Steps explicitly intended for all markets, and Steps with no market:* tag. A Step tagged only for another market is excluded:
{
"id": "europe",
"name": "European Fulfillment",
"selector": {
"language": "jpath",
"script": "$.tags[?@==\"market:europe\" || @==\"market:all\"] ||
!$.tags[?search(@, \"^market:\")]"
}
}
This Space uses European providers and retains Steps that work everywhere. market:all declares universal applicability; no market:* tag admits an unclassified Step by default.
A script selector also expresses what tag-only QBE cannot: checks and combinations involving Step type, effective handling, Attribute contracts, negation, and absence. For example, these selectors choose compensated services and Steps that produce a numeric risk_score, respectively:
$.type == "service" && $.handling == "compensated"
$.attributes.risk_score.role == "output" && $.attributes.risk_score.type == "number"
Provide selector on its own to author one directly. Ejecting QBE keeps its generated JPath as the editable selector and drops the QBE authoring data; scripts are never converted back into QBE.
Space membership is recalculated whenever a Step or Space changes. Changing selector-visible Step metadata moves the Step into or out of each Space whose result changes.
Start a Flow in a Space
Pass space_id when starting a Flow to plan within that Space:
curl -X POST http://localhost:8080/engine/flows \
-H "Content-Type: application/json" \
-d '{
"id": "risk-run-1",
"space_id": "risk",
"goals": ["score-customer"],
"init": {"customer_id": ["cust-123"]}
}'
POST /engine/plan accepts the same field, so a preview reflects the Space the Flow will run in. A Flow Step accepts flow.space_id to scope its child Flow, described in Flows.
Manage Spaces
| Request | Result |
|---|---|
GET /engine/spaces | Every registered Space |
POST /engine/spaces | Register a Space |
POST /engine/spaces/preview | The normalized Space and selected Step IDs, without registering it |
GET /engine/spaces/{space_id} | One Space definition |
GET /engine/spaces/{space_id}/steps | The Steps the Space selects right now |
PUT /engine/spaces/{space_id} | Replace a Space definition |
DELETE /engine/spaces/{space_id} | Remove a Space |
GET /engine/spaces/{space_id}/steps answers membership questions for a registered Space. To try a selector before registering it, post the same body you would register to POST /engine/spaces/preview, which evaluates it against the current catalog and returns the Step IDs it selects:
curl -X POST http://localhost:8080/engine/spaces/preview \
-H "Content-Type: application/json" \
-d '{"qbe": [["domain:risk"]]}'
{
"space": {
"qbe": [["domain:risk"]],
"selector": {
"language": "jpath",
"script": "$.tags[?@==\"domain:risk\"]"
}
},
"step_ids": ["score-customer", "screen-sanctions"]
}
The returned space is normalized by the engine and includes the authoritative selector. A Space still being drafted can be checked before it has an id or a name; the client does not need to generate a selector from its QBE.
Referential Integrity
A Flow Step that names a Space anchors the definitions it depends on. While that reference exists, Argyll keeps the arrangement consistent:
- Deleting the Space returns
409 Conflictand names the Flow Step holding it - Deleting a Step used as one of that Flow Step’s Goals returns
409 Conflict - Replacing the Space with a selector that would drop one of those Goals returns
409 Conflict - Changing such a Goal’s selector-visible metadata so it leaves the Space returns
400 Bad Request