RECTGTN — the planner model behind plan / replan

Copy Markdown View Source

RECTGTN stands for Relationship-Enabled Capability-Temporal Goal-Task-Network — the HTN (Hierarchical Task Network) planning model exposed over MCP by the plan and replan tools. This page defines the JSON-LD shapes a domain may use.

The three task kinds

Everything in a domain's tasks list (and in each method's subtasks) is one of three kinds:

KindJSON-LD formMeaning
TwCalla call array [name, arg…]name in actions → a primitive that runs; name in methods → a compound task decomposed via alternatives
TwGoalthe goals key (array or object form)desired (pointer, value) bindings, satisfied via goal methods
TwMultiGoala tasks entry {"multigoal": {…}}a set of bindings the planner backjumps over, choosing which to satisfy first

TwCall — call arrays

{"@context": {"khr": "https://registry.khronos.org/glTF/extensions/2.0/KHR_interactivity/",
              "domain": "khr:planning/domain/"},
 "@type": "domain:Definition", "name": "demo",
 "variables": [{"name": "done", "init": {"a": false, "b": false}}],
 "actions": {"do_a": {"params": [], "body": [{"pointer/set": "/done/a", "value": true}]},
             "do_b": {"params": [], "body": [{"pointer/set": "/done/b", "value": true}]}},
 "methods": {"top": {"params": [], "alternatives": [{"name": "seq",
                                                     "subtasks": [["do_a"], ["do_b"]]}]}},
 "tasks": [["top"]]}

Effects use pointer/set; guards use {"eval": {"type": "math/<op>", …}}.

TwGoal — goals

A conjunction of desired (pointer, value) bindings, in two shapes:

{"@type": "domain:Problem", "name": "switch_goal",
 "variables": [{"name": "switch", "init": {"x": false}}],
 "goals": [{"pointer": "/switch/x", "eq": true}]}
{"@type": "domain:Definition", "name": "blocks",
 "goals": {"pos": {"params": ["block", "dest"],
                   "alternatives": [{"name": "stack_it", "subtasks": [["stack", "block", "dest"]]}]}}}

TwMultiGoal — multigoals

{"@type": "domain:Problem", "name": "switch_multigoal",
 "variables": [{"name": "switch", "init": {"x": false, "y": false}}],
 "tasks": [{"multigoal": {"switch": {"x": true, "y": true}}}]}

A tasks list may freely mix call arrays and multigoal objects: "tasks": [["move_one", "a", "table"], {"multigoal": {"pos": {"c": "b"}}}].

Relationships and capabilities — one ReBAC graph

RECTGTN's "Relationship-Enabled" and "Capability" are the same engine: a domain's capabilities object is compiled into a ReBAC (Relationship-Based Access Control) graph — the same graph Taskweft.ReBAC (taskweft_rebac) exposes standalone via Taskweft.rebac_check/5/Taskweft.rebac_expand/4 — and action guards are evaluated against it, so a capability requirement can be a composed relation expression, not just a direct edge (ADR 0004).

A graph is {"edges": [{"subject", "rel", "object"}], "definitions": {}} — field order matches the canonical Lean model's ⟨subject, RelationType, object⟩ tuple (Planner.Capabilities); JSON keys are looked up by name, not position, so this is purely for readability/consistency, not a wire format requirement. Relations are HAS_CAPABILITY, CONTROLS, OWNS, IS_MEMBER_OF, DELEGATED_TO, SUPERVISOR_OF, PARTNER_OF, CAN_ENTER, CAN_INSTANCE.

graph =
  Taskweft.ReBAC.new_graph()
  |> Taskweft.ReBAC.add_edge("alice", "team_x", "IS_MEMBER_OF")
  |> Taskweft.ReBAC.add_edge("team_x", "resource_1", "OWNS")

Taskweft.ReBAC.check_rel(graph, "alice", "OWNS", "resource_1")
# => true, via the IS_MEMBER_OF -> OWNS chain

Relation expressions compose beyond a single relation name: union, intersection, difference, and tuple_to_userset (follow a pivot_rel chain, e.g. membership, before checking the inner expression).

Capabilities and temporal duration

A TwCall, TwGoal, or TwMultiGoal domain may add either or both.

Capabilities — a top-level capabilities object binds which entities hold which capabilities, which capabilities each action requires, and (optionally) an explicit relationship graph:

"capabilities": {
  "entities": {"<entity>": ["<cap>", ...], ...},
  "actions":   {"<action>": ["<cap>", {"rel": <relation-expression>, "object": "<obj>"}, ...], ...},
  "graph":     {"edges": [...], "definitions": {}}
}

entities compiles to direct HAS_CAPABILITY edges. Each actions entry is either a bare capability name (sugar for a direct HAS_CAPABILITY check) or a full {"rel": expr, "object": obj} requirement — e.g. an agent qualifies if it's a member of a team that holds the capability, not just a direct holder:

"capabilities": {
  "graph": {"edges": [{"subject": "alice", "rel": "IS_MEMBER_OF", "object": "flight_team"},
                      {"subject": "flight_team", "rel": "HAS_CAPABILITY", "object": "fly"}]},
  "actions": {"a_fly": ["fly"]}
}

An action only applies to an agent for whom every requirement holds; the planner tries the next alternative otherwise.

Temporal duration — any action may carry "duration": "<ISO 8601>" (e.g. "PT5M"); actions without one default to "PT0S". Every plan response includes a "temporal" block computed from these durations, with no separate call needed:

{"plan": [["a_fly", "drone_1", "city"]],
 "temporal": {"consistent": true, "origin": "PT0S", "total": "PT5M",
              "steps": [{"action": "a_fly", "duration": "PT5M", "start": "PT0S", "end": "PT5M"}]}}

Soundness contract

Replanning a returned plan with fail_step == -1 reports the whole plan complete:

{:ok, plan_json} = Taskweft.plan(domain_json)
{:ok, out}       = Taskweft.replan(domain_json, plan_json, -1)
env = Jason.decode!(out)
env["fail_step"]       == -1
env["completed_steps"] == length(Jason.decode!(plan_json))

See also

  • ADR 0001 — the action-body node shape (eval + pointer/set).
  • ADR 0002 — the glTF Interactivity node catalog conventions.
  • ADR 0003 — the planned flow-graph execution engine.
  • ADR 0004 — unifying domain capabilities with the ReBAC relation-expression engine (issue #96).