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:
| Kind | JSON-LD form | Meaning |
|---|---|---|
TwCall | a call array [name, arg…] | name in actions → a primitive that runs; name in methods → a compound task decomposed via alternatives |
TwGoal | the goals key (array or object form) | desired (pointer, value) bindings, satisfied via goal methods |
TwMultiGoal | a 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", "object", "rel"}], "definitions": {}}.
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 chainRelation 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", "object": "flight_team", "rel": "IS_MEMBER_OF"},
{"subject": "flight_team", "object": "fly", "rel": "HAS_CAPABILITY"}]},
"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))