원클릭으로
max-patch-agent
Generate MAX patches with control flow, message routing, subpatcher organization, and MIDI handling
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Generate MAX patches with control flow, message routing, subpatcher organization, and MIDI handling
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Generate Gen~ GenExpr DSP code, signal processing patches, and audio effect chains
Design and position UI controls for MAX patches in presentation and patching mode
Orchestrates the generate-review-revise loop using Python critic modules for quality assurance
Manages MAX project creation, status tracking, project switching, and test protocol execution
Generate JavaScript code for Max js objects (V8) and Node for Max scripts (N4M)
RNBO export-aware patch generation, target validation, and param mapping
| name | max-patch-agent |
| description | Generate MAX patches with control flow, message routing, subpatcher organization, and MIDI handling |
| allowed-tools | ["Read","Write","Bash","Glob","Grep"] |
| preconditions | ["Active project must exist","Router must have dispatched to this agent"] |
The Patch agent generates MAX/MSP .maxpat files focused on control-rate operations: object routing, message passing, subpatcher organization, MIDI handling, and data management.
Before any generation:
CLAUDE.md at project root -- follow all 5 rules and patch style guidelinesObjectDatabase from src.maxpat.db_lookup for all object lookups -- it loads all domains, resolves aliases, checks PD blocklist, and provides relationship data automatically.claude/max-objects/relationships.json for common object pairings (if needed for design decisions)config.json via load_project_config() from src.maxpat.project for allowed packages. Pass allowed_packages to Patcher(allowed_packages=allowed) so package objects outside the project's selection are blocked at creation time.Domain focus: Max control/data/UI objects. Signal processing and RNBO are handled by their respective agents.
Patcher instances with boxes and connections via src.maxpat.patcherBox constructor for all standard objects (validates against ObjectDatabase)Box.__new__() bypass for structural objects: subpatchers, bpatcherPatcher.add_connection(src_box, src_outlet, dst_box, dst_inlet)Patcher() -- create a new patchBox(name, args, db) -- create a validated boxPatcher.add_box(box) -- add box to patchPatcher.add_connection(src_box, src_outlet, dst_box, dst_inlet) -- connect boxesPatcher.add_subpatcher(name, inlets, outlets, inlet_comments, outlet_comments) -- add a subpatcher with labeled I/OPatcher.populate_assistance_comments() -- auto-fill empty inlet/outlet comments from connection contextfinalize_patch(patcher, is_new=True) -- single-call layout cleanup: styling, layout, comments, midpoints (new); midpoints + comments (edit)apply_layout(patcher, layout_options=None) -- row-based topological layout positioning (accepts LayoutOptions)validate_patch(patcher.to_dict(), db=patcher.db) -- run four-layer validation pipelinesave_patch_roundtrip(patcher.to_dict(), path) -- write .maxpat to diskPatcher.add_comment(text, x, y) -- add a comment box (for inline annotations, critic notes)Patcher.add_message(text, x, y) -- add a message box (for triggering messages, storing values)Patcher.add_node_script(filename, code=None, num_outlets=2, x, y) -- add a node.script box for Node for Max (returns tuple of Box and code string)Patcher.add_js(filename, code=None, num_inlets=1, num_outlets=1, x, y) -- add a js object box for V8 JavaScript (returns tuple of Box and code string)#N tokens in bpatcher subpatches must be standalone (space-delimited), never embedded in compound stringsbuffer~ slot-#1 -- compound substitution fails silently in MAXbuffer~ #1 with bpatcher arg "slot-1" -- standalone token works correctly#1, #2, etc.)["slot-1", "slot-1-out"] where #1 = buffer name, #2 = send nametrigger (t) for ALL control-rate fan-out -- connecting one outlet to 2+ destinations without trigger is a structural defect (see shared-capabilities.md "Control-Rate Fan-Out Rule")trigger to guarantee ordering (CLAUDE.md Rule #3).claude/skills/references/ui-presets.mdShared Capabilities: See
.claude/skills/references/shared-capabilities.mdfor Control-Rate Fan-Out Rule, Assistance Comments, Aesthetic Capabilities, Layout Options, Editing Functions, and Edit Workflow reference.
Four high-level builders on Patcher codify recipes that previously lived as
prose in CLAUDE.md. Prefer these over restating the recipes per patch.
Patcher.add_overlay_readout(target, *, format='%.2f', type='flonum', editable=False, offset_x=0, offset_y=0) -> BoxCreate a flonum/comment/number readout overlapping a target dial or numeric
control. Codifies the CLAUDE.md §"Rule #6: Z-Order Awareness" overlay
recipe — bakes in bring_to_front (overlay renders on top) and
ignoreclick=1 (clicks pass through to underlying control).
target: The Box to overlay (typically dial or another numeric control).format: printf-style format string (e.g. '%.2f'). For
type='flonum'/type='number', the builder translates '%.Nf' to
extra_attrs["numdecimalplaces"]=N (flonum/number have no format
attribute — MAX would silently drop a literal format key). Format
strings with literal text or non-%.Nf patterns (e.g. '%.1f Hz',
'%d', '%.2g') raise ValueError on flonum/number; use
type='comment' + a separate prepend chain for unit text display. For
type='comment', the format kwarg is informational only (comments
display literal text — no native formatting attribute exists).type: 'flonum' (default), 'comment', or 'number'.editable: Default False bakes ignoreclick=1. Pass True for the rare
M4L case where the readout itself is editable.offset_x / offset_y: Fine-tune position relative to target's rect.When to call: Anytime you create a dial-with-flonum-readout pattern. Replaces the 5-step manual recipe in CLAUDE.md §"Rule #6".
Patcher.add_labeled_param_bank(params, x, y, *, label_side='left', extra_attrs=None) -> tuple[Box, list[Box]]Build a multislider parameter bank with aligned comment labels. Codifies
CLAUDE.md §"Multislider as Labeled Parameter Bank" — bakes in
size=len(params), height=size*24, orientation=0, contdata=1,
setstyle=1, setminmax=[min(mins), max(maxes)].
params: List of (name, min, max) tuples — one bar per tuple.x / y: Multislider position. Labels start at the same y.label_side: Only 'left' is supported in Phase 31.extra_attrs: Optional dict deep-merged over baked defaults (caller wins
on collision).Returns (multislider, [comment, ...]). Caller wires fetch $1 to the
multislider input themselves and reads values from the multislider's RIGHT
outlet (outlet 1) — see memory feedback_multislider_fetch.md.
When to call: Anytime you need a vertical bank of N labeled parameter
bars with shared range. For widely-varying ranges, prefer individual
dial+flonum overlays (the multislider envelope setminmax does not
enforce per-bar limits).
Patcher.add_m4l_gen_synth(params, *, gen_varname='synth', gen_code=None) -> tuple[Box, list[Box], Box]Build a Live-ready M4L gen synth skeleton: gen~ (with stable varname),
one live.dial per param (bound via param_connect), and plugout~
directly fed by gen~'s outlet (NO gain~/live.gain~/ezdac~ between —
CLAUDE.md M4L rule).
params: List of (name, min, max) tuples — one live.dial per param.
Names MUST be valid MAX symbols.gen_varname: gen~'s varname (default 'synth'). Must be unique per
patcher when adding multiple skeletons.gen_code: Optional GenExpr body. If None, an empty body
(Param declarations + out1 = 0;) is emitted so gen~ compiles. Caller
replaces with real DSP.Returns (gen, [live_dial, ...], plugout). Each live.dial has the full
param_connect: "<gen_varname>::<name>" + parameter_enable=1 +
saved_attribute_attributes.valueof block. The skeleton is polish-ready;
run polish_m4l_device(patcher.to_dict()) afterward if desired (do NOT
call from inside the builder — layering violation).
When to call: Starting a new M4L .amxd device with a synth/effect
body. Replaces the manual param_connect setup recipe.
apply_layout)apply_layout consults a _ROLE_COMPANION_MAP in layout.py that maps
signal_role → companion placement using the Phase 28/30 signal_role
schema. Mapping (Phase 31 D-14):
| Source outlet role | Companion | Placement |
|---|---|---|
audio | meter~ | right of source |
status | flonum | overlay (on top, ignoreclick=1) |
trigger/float/data/list | (none) | (caller decides) |
Falls through to the legacy _COMPANION_NAMES heuristic when a source
outlet's role is None (unaudited). What this means for agents: when
you add a meter~ connected to an audio outlet, you don't need to position
it manually — apply_layout does it via the role map. Same for flonum
overlays on status outlets. For other roles, position the companion
yourself or use add_overlay_readout explicitly.
| Use case | Builder | Replaces (CLAUDE.md) |
|---|---|---|
| dial+flonum readout overlay | add_overlay_readout(dial) | §"Rule #6: Z-Order Awareness" recipe |
| N-parameter labeled multislider | add_labeled_param_bank([(name, mn, mx), ...], x, y) | §"Multislider as Labeled Parameter Bank" recipe |
| M4L gen synth skeleton | add_m4l_gen_synth([(name, mn, mx), ...]) | §"Domain-Specific Rules → Max for Live (M4L)" recipe |
| Auto-place meter~ on audio outlet / flonum on status outlet | (none — apply_layout does it via _ROLE_COMPANION_MAP) | manual companion positioning |
When generating patches with package objects (BEAP, Vizzie, etc.), read .claude/max-objects/PACKAGES.md for:
BEAP modules are bpatchers that emulate analog modular synthesis:
add_bpatcher(object_name="bp.Oscillator", filename="bp.Oscillator.maxpat") for auto-sized placementCommunity packages (FluCoMa, CNMAT, Bach, Odot, ml-lib, IRCAM Spat, Cage, Dada, EARS, Rhythmic Time Toolkit) have stub DB entries but require local extraction before generation.
Before using any community package object:
ObjectDatabase().get_package_info(package_name)["extracted"]false: inform the user they must install and extract first (see max-lifecycle SKILL.md for exact messages)true: proceed normally -- extracted data is verified and safe for generationPackage-specific notes:
fluid.*): Audio analysis/decomposition. Signal objects + offline buf* objects.resonators~, analyzer~).bach.*): Uses llll data type (NOT standard MAX lists). Never mix llll with regular lists. Use bach.list2llll/bach.llll2list for conversion.o.*): OSC bundle-based expression language.ml.*): Machine learning. All follow add/train/map pattern.spat5.*): Spatial audio. Parameters via OSC bundles.rtk.*): Signal-rate RNBO sequencing.Vizzie modules pass Jitter matrices (video frames) between bpatchers:
add_bpatcher(object_name="vz.playr", filename="vz.playr.maxpat") for auto-sized placementStructured workflow blueprints for generating working package patches. Each template specifies objects, connection order, I/O types, parameter ranges, and gotchas. Templates are generation guidance -- not pre-built .maxpat files.
Use case: Build nested list structures for algorithmic composition Chain: data source (message/number) -> bach.list2llll -> bach.join / bach.flat / bach.nth (llll manipulation) -> bach.score or bach.roll
| # | Source | Outlet | Destination | Inlet | Type |
|---|---|---|---|---|---|
| 1 | (MAX list source) | 0 | bach.list2llll | 0 (list in) | list |
| 2 | bach.list2llll | 0 (llll out) | bach.join | 0 (llll in 1) | llll |
| 3 | bach.list2llll | 0 (llll out) | bach.join | 1 (llll in 2) | llll |
| 4 | bach.join | 0 (joined llll) | bach.flat | 0 (llll in) | llll |
| 5 | bach.flat | 0 (flattened llll) | bach.nth | 0 (llll in) | llll |
| 6 | bach.nth | 0 (extracted element) | bach.score | 0 (llll data) | llll |
Parameter ranges:
@numins default 2 (number of llll inlets to join)bach.nth 1 extracts the first element)Gotchas:
Use case: Display and edit musical notation with bach.score or bach.roll Chain: bach.score (or bach.roll) <- llll data via inlet 0; message box commands (addchord, delete) via inlet 0
| # | Source | Outlet | Destination | Inlet | Type |
|---|---|---|---|---|---|
| 1 | (llll data source) | 0 | bach.score | 0 (llll data / commands) | llll |
| 2 | message box ("addchord ...") | 0 | bach.score | 0 (command) | message |
| 3 | bach.score | 0 (modified llll) | (downstream bach processing) | 0 | llll |
| 4 | bach.score | 1 (notifications) | (status display) | 0 | list |
Parameter ranges:
Gotchas:
Use case: Generate musical material algorithmically and display in notation Chain: algorithm source (metro + counter / random) -> bach.list2llll -> bach.collect -> bach.quantize -> bach.score
| # | Source | Outlet | Destination | Inlet | Type |
|---|---|---|---|---|---|
| 1 | metro | 0 (bang) | trigger b b | 0 | bang |
| 2 | trigger | 0 | random 12700 | 0 (bang) | bang |
| 3 | random | 0 (pitch value) | bach.list2llll | 0 | list |
| 4 | bach.list2llll | 0 (llll) | bach.collect | 0 (llll in) | llll |
| 5 | trigger | 1 | bach.collect | 0 (bang to flush) | bang |
| 6 | bach.collect | 0 (collected llll) | bach.quantize | 0 (llll in) | llll |
| 7 | bach.quantize | 0 (quantized llll) | bach.score | 0 (llll data) | llll |
Parameter ranges:
Gotchas:
Domain focus: Edit control flow routing, message handling, subpatcher organization.
finalize_patch(patcher, is_new=True) -- applies styling, layout, assistance comments, and midpoint generation for all patchers and subpatcherspatch_dict = patcher.to_dict(), results = validate_patch(patch_dict, db=patcher.db)(patch_dict, results) tuple for critic reviewsave_patch_roundtrip(patch_dict, path) to project's generated/ directoryread_patch() and patcher.analyze()finalize_patch(patcher, is_new=False) -- regenerates cable midpoints and populates assistance comments without repositioning existing objectsvalidate_patch(patcher)save_patch_roundtrip()