Skip to content

Persistence truth ​

The engine's save rules are subtle: Godot packs a node only when its owner is the edited root or an editable instance, and a skipped node's subtree is never visited. So a change that applies live in the editor can silently vanish on save — the worst outcome for an agent that trusts ok: true.

The persistence system stamps every mutation result with what a save will keep:

jsonc
{
  "node_path": "Relic/Cold/Extra",
  "created": true,          // the change applied live
  "persisted": false,       // but the save will drop it
  "reason": "instanced_child_not_editable",
  "hint": "'Relic/Cold' is inside the instanced scene 'Relic', which does not
           have Editable Children enabled — the change shows in the editor but
           will not be saved. Enable Editable Children on 'Relic', or target a
           node the scene owns."
}

The rule ​

_persistent_target(node) (in command_router.gd) mirrors 4.7's SceneState::_parse_node:

Reasons are stable tokens the agent can match on:

TokenCause
node_not_ownedno owner in the edited scene (e.g. added by a @tool script)
instanced_child_not_editableinside an instanced scene without Editable Children
embedded_in_other_resourcethe edit reaches through a resource embedded in another scene, never re-saved
group_from_base_scenethe group an instanced/inherited scene gives the node is back after reload

The deciding rule per mutation family ​

A change is saved only when the packer visits the changed node, so the verdict keys on the nearest ancestor the packer actually writes:

FamilyKeys onWhy
creates / instances / duplicates / composethe parentthe target does not exist yet; if the parent is skipped, the whole new node is lost
move_nodethe destination parenta node moved into a non-editable instance is lost; the removal from an owned source saves fine
delete_nodethe node itselfan instance-owned node is restored by the instance on reload; an unowned node was never packed
property edits / resourcesthe node, then the resource chaina sub-resource saves with its node; a resource file saves itself; a sub-resource of another scene is never re-saved

Previews agree with reality ​

A dry_run preview carries the same verdict as the real run. The tool sends the read-only cmd_node_persistence probe (target path, plus the resource_properties the real handler would follow, or probe_parent / probe_parent_of for structural edits), and the addon runs the same rule. The probe and the real handler always agree — the e2e suite asserts preview == real == saved file for every family.

Per-target verdicts for batches ​

batch_set_property and apply_node_edits target resolution descends into instanced children, so their applied[]/edited[] entries are not uniformly persistent. The batch result carries a persistence[] array — one {node_path, persisted, reason?, hint?} entry per applied target. A batch is never "ok" in a way that hides a lost target.

The dead end, closed ​

instanced_child_not_editable hints used to say "Enable Editable Children on '<instance>'" with no tool to do it. 2026.09.19 added godot_scene_edit_set_editable_children — the action the verdict names — and the scene tree now marks instanced nodes with owner / editable_children: true so the agent can see the situation before acting.

Where it is enforced ​

  • Addon: _persistent_target, _resource_persistence, _group_removal_persistence in command_router.gd; every Tier-1/2/3 handler stamps via _with_persistence.
  • Previews: mcp_server/tools/_persistence.py builders (node_probe, animation_probe, parent_of_probe) — the single source of truth for the probe the tool sends.
  • e2e: test_persistence_e2e.py — preview == real == saved file for ~40 operations across instanced, inherited, and resource-chain cases.
  • Contract version: additive fields only; contract_version stays 1.

194 tools · 29 toolsets · contract version 1