LLMSkillHub
mirroredCopied from dylanroscover/Embody under MIT. dylanroscover has not claimed this page, and is not affiliated with LLM SkillHub.

dylanroscover / multi-instance

Workflow for running multiple TD instances with Envoy, switching between them, and understanding the instance registry.

0
SKILL.md
7.2 KB

Multi-Instance Workflow

Envoy supports multiple TouchDesigner instances running simultaneously in the same git repo. Each instance gets its own port, and the bridge can switch between them on demand.

Architecture

Claude Code  <-->  STDIO Bridge  <-->  Envoy (TD instance A, port 9870)
                        |
                        +---switch--->  Envoy (TD instance B, port 9871)
  • One bridge process per SESSION (spawned by each AI client's MCP client)
  • Per-session pinning - each bridge pins to an instance NAME and re-resolves its port from the registry every tick, so a pinned instance restarting on a new port still self-heals, and a save that renames its .toe is followed (same TD process). The registry active field only seeds NEW bridges without a pin.
  • A vanished pin fails closed - if the pinned instance leaves the registry (closed, crashed), the session has no target: calls return envoy.instance.unavailable and nothing is forwarded - never to another instance - until it re-registers (a call that names another instance with instance= still goes there). Recover with launch_td (reopens the pinned .toe) or switch_instance.
  • Per-call addressing - every Envoy tool accepts an optional instance argument; the bridge routes that one call to the named instance and leaves the pin alone (unknown or unreachable names fail only that call with error_code envoy.instance.unknown / envoy.instance.unreachable). Prefer it over switching when you only need one look at another instance.
  • Switching is instant and session-local - switch_instance re-pins THIS session's bridge in-memory; peers are untouched unless you pass all_sessions=True (writes the registry default and bumps active_epoch, which moves every session)
  • Registration never re-routes running sessions - a new instance takes the active default slot only when it is vacant or names a dead instance

Instance Registry (.embody/envoy.json)

Each Envoy instance registers itself in .embody/envoy.json at the git root on startup:

{
  "active": "Embody-5.257",
  "td_executable": "/Applications/TouchDesigner.app",
  "instances": {
    "Embody-5.257": {
      "toe_path": "dev/Embody-5.257.toe",
      "port": 9870,
      "td_pid": 12345
    },
    "MySecondProject": {
      "toe_path": "MySecondProject.toe",
      "port": 9871,
      "td_pid": 67890
    }
  }
}
FieldPurpose
activeDefault instance for NEWLY-spawned bridges (each bridge then pins per-session; only an active_epoch bump re-targets pinned bridges)
td_executablePath to TD app (used by launch_td)
instances.<name>.toe_pathRelative path to the .toe file
instances.<name>.portEnvoy HTTP port for this instance
instances.<name>.td_pidOS process ID of the TD process

Instances are added on Envoy startup and removed on graceful shutdown (onDestroyTD).

Port Allocation

Each instance picks a port from a 10-port range starting at the configured Envoyport parameter (default: 9870):

  1. Try the base port first
  2. If occupied by this TD process's old server, force-close it and reclaim
  3. If occupied by another process, scan base+1 through base+9
  4. First available port wins; if all 10 are taken, startup fails

This means up to 10 simultaneous instances per base port.

Workflow: Running Multiple Instances

1. Open the first .toe file

Launch TD normally or via launch_td. Envoy starts on its configured port and registers in .embody/envoy.json.

2. Open additional .toe files

Open them in separate TD instances (File > Open or double-click). Each Embody/Envoy auto-starts and claims the next available port.

3. List instances

Call switch_instance with no parameters to see all registered instances and their status:

switch_instance()

Returns each instance's name, port, PID, reachability (PID alive + port responding), and whether it's the active target.

4. Switch to a different instance

switch_instance(instance="MySecondProject")

The bridge immediately redirects to the target instance's port. All subsequent MCP calls go to that TD process.

5. Switch back

switch_instance(instance="Embody-5.257")

Reachability Checks

An instance is reachable only when:

  • Its registered PID is alive (kill -0 / process table check)
  • Its registered port responds to a TCP connect

Both conditions must be true. A dead PID with an open port means another instance reused that port - the entry is stale.

get_td_status includes the full instance registry with reachability in its response.

Stale Instance Cleanup

Instances are deregistered on graceful TD shutdown. If TD crashes:

  • The PID becomes dead, so the instance shows as unreachable
  • The port may be freed, allowing a new instance to claim it
  • Stale entries remain in .embody/envoy.json but are filtered by reachability checks
  • Re-launching TD with the same .toe overwrites the stale entry

Closing Instances

Preferred: Close TD instances via Envoy by calling execute_python with project.quit(). This prompts the user to save unsaved changes, then triggers onDestroyTD for clean deregistration and port release. Works reliably across platforms.

To close a specific instance, switch_instance to it first, then send the quit command:

switch_instance(instance="MySecondProject")   # target the instance
execute_python(code="project.quit()")         # user gets save prompt in TD

Never use project.quit(force=True) unless the user has explicitly asked - it skips the save dialog and risks losing unsaved work.

Avoid: osascript -e 'quit app "TouchDesigner"' and similar OS-level approaches are unreliable - they may not target the correct instance and don't guarantee clean Envoy shutdown.

Common Scenarios

ScenarioAction
Test code in two .toe files side by sideOpen both, switch_instance between them
Test code in a secondary .toeOpen it, switch to it, perform work, switch back
Check if a second TD is still runningswitch_instance() (list mode) or get_td_status
One instance crashed, want the otherswitch_instance to the surviving instance
Close a specific instanceswitch_instance to it, then execute_python with project.quit()

Same-Project Instances

When you open the same .toe file in multiple TD instances, Envoy auto-suffixes the registry key to avoid collisions. The first instance registers as MyProject, the second as MyProject-2, etc. Stale entries (dead PIDs) are automatically reclaimed.

Limitations

  • The bridge connects to one instance at a time - no parallel MCP calls to multiple instances
  • Maximum 10 instances per base port range
  • .embody/envoy.json is per git root - instances in different repos have separate registries
  • launch_td and restart_td act on this session's pinned instance and its .toe (unpinned: the registry default) - pass project_path to open another file
  • Opening the same .toe file in multiple instances auto-suffixes keys (MyProject-2, -3, etc.)