# Externalize Operator

> MUST READ before calling externalize_op or save_externalization. Required workflow steps.

- Skill: `dylanroscover/externalize-operator` (Agent Skill)
- Install (CLI): `npx skillmds@latest add dylanroscover/externalize-operator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dylanroscover/externalize-operator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: dylanroscover (https://skillmd.com/u/dylanroscover)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dylanroscover/externalize-operator

---

<!-- Generated by Embody/Envoy - Do not remove this comment -->

# Externalize Operator Workflow

## Tagging (includes save)

`externalize_op` tags the operator AND writes it to disk in one step (it calls `Update()` internally). No separate save is needed.

1. **Tag and externalize**: `externalize_op` on the operator (auto-detects type if omitted)
2. **Verify**: `get_externalization_status` to confirm dirty state and file path
3. **Inspect**: Verify file exists in `embody/` via file inspection

## Re-exporting After Changes

`save_externalization` force re-exports an already-externalized operator. Use it after modifying an operator in TD when you need to update its file on disk.

## Excluding Runtime State from TDXN Exports (`tdxn_exclude:<par>` tags)

When a parameter holds **runtime state** (a session file path, a negotiated port, a live readout) rather than configuration, keep its value out of committed `.tdxn` files by tagging the operator itself - one tag per parameter, using the colon-suffixed form of the exclude tag:

```python
op('deck_a').tags.add('tdxn_exclude:file')
```

The operator and its other parameters export normally; the named parameter's constant value is omitted (expressions/binds still export - references are configuration). The bare `tdxn_exclude` tag on a COMP is different: it makes the whole COMP invisible to TDXN. The suffixed tag round-trips in the `.tdxn`, so the omission is visible and survives reconstruction; it takes effect at the COMP's next export (Save tdxn, Update, or project save). A tag naming a nonexistent parameter logs a WARNING at export. Users can manage these visually: the tagger's Actions menu on a TDXN COMP has **Exclude from tdxn**, a drop-zone panel that toggles `tdxn_exclude:<par>` for dragged parameters and whole-COMP `tdxn_exclude` for dragged COMPs, listing every exclusion in the subtree with a per-row **x** to remove it.

A third form targets a DAT's live **contents** instead of a parameter: `tdxn_exclude:dat_content` (a reserved name, so it never trips the unknown-parameter warning). Use it for DATs whose rows are runtime state with no authored value -- a log ring buffer, a status readout. Those are normally force-captured even when Embed DATs is OFF, because Embody refuses to drop content that exists nowhere else on disk; this tag is the only sanctioned way past that net, so apply it only where losing the content is the intent. The operator itself still exports in full (parameters, position, wiring, tags) and returns empty on reconstruction, and the save-time content check reads the same rule, so it never flags the DAT or files it as its own file.

## Creating Python Files for TouchDesigner

When creating Python files (scripts, extensions, test files, callbacks):
1. Create the textDAT in TouchDesigner first (via MCP `create_op` or in TD UI)
2. Write the Python code into the DAT (via MCP `set_dat_content`)
3. Tag the DAT for externalization (`externalize_op`) - Embody writes the `.py` file to disk

**NEVER** manually set the `file` and `syncfile` parameters - Embody handles all file path management.

## Exporting a Portable Tox

Export any COMP as a self-contained `.tox` with all Embody metadata stripped:

- **Via MCP**: `execute_python` with `op.Embody.ExportPortableTox(target=op('/path/to/comp'), save_path='/output/path.tox')`
- **Via UI**: Manager UI > Actions popup > "Export portable tox"

The exported `.tox` works in any TD project with no missing file errors.

**Release hooks**: a TEXT DAT named `pre_release` that is a DIRECT child of the target runs on a STAGED COPY in `/sys/quiet` - the live comp is never touched, file-sync is disabled on the copy (no write-through to source files), extensions are NOT initialized there and par callbacks don't fire (shape via direct par/DAT/op edits, or use `hook_mode='live'` for extension logic). A raise aborts the export and keeps the copy as `<name>_release_failed` for same-session inspection (`/sys` is not saved; `post_release` skipped). A direct-child Text DAT named `post_release` runs on the ORIGINAL after the save whenever `pre_release` completed - even when the save failed. Both hook DATs are deleted from the copy, so hook code never ships in the artifact. `args[0]` = resolved save path; `post_release` also gets `args[1]` = success. Hooks run synchronously on the main thread - keep them fast, defer uploads. Nested exports from inside a hook run plain (no hooks, no second copy). `hook_mode='live'` restores in-place semantics (mutations persist, hooks ship dormant in the tox); `run_hooks=False` skips hooks and ships them as-is (the self-updater's backup uses it). Exports whose target is - or contains - the live Embody COMP are never copy-staged (Embody-self runs live-mode; hooks on a container holding Embody are skipped with a warning in copy mode). `op.Embody.ReleaseAll(root=None, out_dir=None)` (or the Export All Release Toxes pulse) batch-exports every component that is BOTH Embody-tracked AND hook-bearing - tracked-and-hooked is the opt-in (third-party comps ship with their authors' hook DATs and are never batch-released); per-component failures log and continue.

## Checking Status

- `get_externalizations` - list all externalized operators with status
- `get_externalization_status` - get dirty state, build number, timestamp, file path for a specific operator

## TDXN Export - Palette COMP Handling

When exporting a TDXN-strategy COMP whose network contains TD palette components (e.g. `abletonLink`, Widget components, anything under `Samples/Palette/`), Embody consults the `Tdxnpalettehandling` par on the Embody COMP's TDXN page:

- **Ask** (default): On first encounter of each palette COMP, a four-button dialog appears - *Black Box* (this COMP), *Full Export* (this COMP), *Black Box for All*, *Full Export for All*. The per-COMP choice is stored via `comp.store('_tdn_palette_handling', ...)` so repeated exports don't re-prompt.
- **Black Box**: reference the palette only, emit `"palette_clone": true`, skip internal children. Correct for stock palette COMPs.
- **Full Export**: export all children as if the COMP were a regular user COMP. Use only when palette internals have been heavily customized.

Check and override programmatically: `op.Embody.par.Tdxnpalettehandling = 'blackbox' | 'fullexport' | 'ask'`, or force a specific COMP: `op('/path/to/comp').store('_tdn_palette_handling', 'fullexport')`.

## TDXN Export - Locked Content

A TDXN export keeps the lock flag of a locked TOP, CHOP, SOP or POP but not its frozen data. Each export that writes a COMP's `.tdxn` file (`externalize_op`, `save_externalization`, or a save that re-exports a changed COMP) logs ONE WARNING per exported COMP (it rides back in `_logs`; an unchanged COMP at save time, autosave checkpoints and ad-hoc `export_network` snapshots of a tracked COMP skip it) listing `path (FAMILY, source: none|unknown|recooks)` and the exact remedy call. `externalize_op`, `save_externalization`, `export_network`, and the Autoexternalize step of `create_op`/`copy_op`/`create_extension` never show the locked-content dialog; `execute_python` code that calls Update or saveTDXN still can.

- `source: none`: nothing wired in survives a rebuild. Never unlock it - unlocking leaves it empty.
- `source: unknown`: the source was not traced (a parameter reference, mixed inputs, a large scan). It may have none: treat it as `none`.
- `source: recooks`: unlocking re-cooks it, but the frozen snapshot is replaced, not restored. Unlock only when a fresh cook is acceptable.
- To keep the data, run the `externalize_op('<child COMP>', tag_type='tox')` the WARNING names: that child is stored as a `.tox` and the parent `.tdxn` references it (`tox_ref`). In TD, the dialog's **Switch to TOX** button does the same.

