Documentation menu

DOCUMENTATION / v0.1

Use the smallest
maxforge surface that works.

Compile text into .maxpat files, generate large repeated graphs, or let an HTTP-capable agent inspect and replace an explicitly managed patch scope. These are separate workflows with separate installation requirements.

Node.js 20+ Max 9 package available Protocol v1 experimental
Unofficial project

maxforge is not affiliated with, endorsed by, or sponsored by Cycling '74.

Choose a surface

Do not install the native external just to compile a file. Do not start the HTTP server when a deterministic CLI command is enough. The extra layers only make sense when an agent must inspect or mutate an open Max patch.

Goal Use Requires Max running
Compile or validate .maxdsl npx maxforge No
Use maxforge from Node.js code npm install maxforge No
Generate thispatcher commands Library API Only when applying them
Inspect or replace an open patch with an agent maxforge + its automatic local service Yes
Give an agent reliable operating instructions maxforge Skills No; Skills are instructions

Installation

Run the CLI without a global install

The package is published to npm. Pin a version in automation; use @latest for interactive evaluation.

npx maxforge@latest --help

Install the Node.js library

npm install maxforge

Install the native Max package

  1. 01
    Choose the matching maxforge-vX.Y.Z.zip.
  2. 02
    Extract the top-level maxforge directory into the Max packages directory.
  3. 03
    Restart Max, then create a maxforge object or open its help patch.
macOS~/Documents/Max 9/Packages/
Windows%USERPROFILE%\Documents\Max 9\Packages\
macOS signing

Versioned release archives are Developer ID signed and notarized by CI. Local builds remain ad-hoc signed and are not equivalent release artifacts.

Compile a first patch

Create basic.maxdsl:

basic.maxdslMAXDSL
patch "Basic Synth"

freq = number
mt = mtof
osc = cycle~ 440
mul = *~ 0.5
vol = gain~
dac = ezdac~

freq -> mt -> osc -> mul -> vol -> dac
vol[1] -> dac[1]

Validate, then compile:

npx maxforge@latest validate basic.maxdsl
npx maxforge@latest compile basic.maxdsl -o basic.maxpat

Open basic.maxpat in Max. The generated JSON is ordinary Max patcher data; maxforge is not required to keep the compiled file open.

CLI reference

CommandPurpose
compile input.maxdsl -o output.maxpatCompile DSL to a Max patch.
compile input.maxdsl --clipboardEmit compressed patch text pasteable into Max.
validate input.maxdslParse and validate without writing output.
decompile patch.maxpat -o patch.maxdslRecover structural DSL and explicit positions.
from-clipboard -o patch.maxdslRead compressed patch text from stdin and emit DSL.
plan desired.maxdsl --scope voices -o plan.jsonCreate an ordered managed PatchPlan.
doctor --input input.maxdslValidate project catalogs and derive abstraction ports.
catalog cycle~ --jsonSearch effective project and built-in object metadata.
bundle input.maxdsl -o packageBuild a Max package directory with declared custom dependencies.

Unknown objects fail by default because maxforge cannot safely infer their port shape. Use --allow-unknown only when you accept representative 1-inlet/1-outlet metadata and no upper-bound port rejection; Max must validate the real external.

Core DSL syntax

Patch metadata

patch "Title"
patch "Title" | "Description" | 800x600

Patch metadata is optional and maps to the real Max root patcher title, description, and rect fields. Without a declaration, maxforge stores no explicit title, an empty description, and a 640x480 rectangle; Max may use the saved filename as its visible window title. Quoted metadata uses JSON escapes and may contain |.

Objects

name = type [arguments...] [@attribute value...] [at(x, y[, width, height])]
  • name is unique inside its patcher and becomes a stable generated box ID.
  • The catalog contains 320 Max names/aliases with local identity evidence plus the project-owned maxforge external.
  • Attributes become Max box properties; structural keys such as id, maxclass, text, and patching_rect are reserved.
  • Write \@attribute when the literal @attribute must remain inside a newobj text string.
  • Omit at(x, y) for automatic topological layout.
  • Use at(x, y, width, height) when a resized box must round-trip exactly.
osc = cycle~ 440
freq = number @minimum 0 @maximum 127
label = comment "Frequency"
trigger = message "start"
filter = lores~ 1000 0.5 at(240, 180)
Comments

Lines beginning with # are comments. Inline comments after a statement are not supported.

Catalog evidence and limits

The 321 catalog entries are compiler metadata, not a complete Max Object Reference: 239 use a fixed base shape, 45 use explicit argument rules, and 37 are marked dynamic. Dynamic examples include poly~, bpatcher, gen~, and jit.gl.slab; their stored port counts are representative.

  • Identity evidence comes from Max 9 reference XML, object indexes/mappings, and saved patchers.
  • Embedded Gen, Jitter Gen, and RNBO operators are excluded; they are not ordinary Max patcher boxes.
  • defaultSize and category are maxforge layout/grouping values, not Cycling '74 facts.
  • Use python3 scripts/audit-object-catalog.py in a checkout to repeat the local audit.

See the repository's catalog evidence and limits before adding names or relying on dynamic port metadata.

Project externals and abstractions

Declare third-party externals and reusable .maxpat abstractions in a strict maxforge.config.json. Inline DSL subpatchers remain embedded and need no configuration.

{
  "$schema": "https://2bit.jp/maxforge/schema/config-v1.json",
  "schemaVersion": 1,
  "project": {
    "id": "studio_patchset",
    "name": "Studio Patchset"
  },
  "objects": [{
    "name": "vendor.filter~",
    "kind": "external",
    "ports": {
      "mode": "fixed",
      "inlets": 2,
      "outlets": ["signal", ""]
    }
  }],
  "abstractions": [{
    "name": "studio.voice",
    "path": "./patchers/studio.voice.maxpat",
    "ports": "derive"
  }]
}
  • compile, validate, plan, and bundle search upward from the input DSL; --config selects a file explicitly.
  • doctor validates root/imported catalogs and reads referenced abstraction metadata before compilation.
  • catalog [query] --json lists project metadata or searches built-ins when a query is supplied.
  • Fixed ports are exact. Bounded argument mode derives counts from one integer argument or argument count. Dynamic ports require a representative shape but do not impose a false upper connection bound.
  • ports: "derive" counts root inlet/outlet boxes and infers signal outlets from incoming source metadata.
  • An abstraction name must match its existing .maxpat filename. Normal compilation validates but does not embed it, so Max still needs its directory on the search path. bundle copies referenced declared dependencies instead.
  • Imported catalogs cannot recursively import more catalogs. Accidental name collisions fail unless override: true is explicit.
  • project.id is the stable HTTP persistence namespace. Imported catalogs cannot declare it, and unrelated projects must not share it.

Use the public project config schema and shared catalog schema. A valid catalog is compiler metadata only: it does not install an external, configure Max's search path, or prove runtime availability.

bundle writes the main patch under patchers/, follows declared abstraction dependencies, copies declared .mxo/.mxe64 artifacts under externals/, and writes package-info.json. Missing dependency paths and destination basename collisions are errors. Object arguments that refer to arbitrary media or data files are not inferred and must be packaged separately.

for, if, and arithmetic

Expansion runs before ordinary parsing. This is the feature intended for large families of similar Max objects.

voice_bank.maxdslMAXDSL
dac = ezdac~ at(430, 420)

for i in 0..7 {
  osc_${i} = cycle~ ${110 + i * 27.5} at(${40 + i * 110}, 80)
  amp_${i} = *~ 0.125 at(${40 + i * 110}, 140)
  osc_${i} -> amp_${i} -> dac

  if i < 4 {
    meter_${i} = meter~ at(${40 + i * 110}, 210)
    amp_${i} -> meter_${i}
  }
}
  • 0..7 is inclusive.
  • Use for i in 0..6 step 2 for a stepped range.
  • Expressions support loop variables, parentheses, comparisons, + - * / %, and ! && ||.
  • if supports an optional else block, including conventional } else { syntax.
  • Expansion rejects non-finite arithmetic and is capped at 100,000 loop iterations and output lines.
  • ${expr} works in names, arguments, attributes, positions, and connections.
  • Expressions are numeric only. There are no functions, arrays, strings, or modulo operator.

Connections and ports

a -> b -> c
source[1] -> destination[2]

Ports are zero-indexed from the left. A source suffix selects an outlet; a destination suffix selects an inlet. maxforge validates indices when it knows the object shape. It deliberately skips a fabricated upper bound for dynamicPorts and --allow-unknown objects; Max remains the final authority for those connections.

Objects such as gate, route, pack, unpack, trigger, matrix~, and selector~ derive port counts from their arguments.

Subpatchers

fx = p delay_fx {
  input = inlet signal "audio input"
  output = outlet signal "audio output"
  buffer = tapin~ 500
  tap = tapout~ 250
  feedback = *~ 0.4

  input -> buffer -> tap -> feedback -> buffer
  tap -> output
}

Subpatchers may be nested. Only the real Max objects inlet and outlet are emitted, and they are valid only inside a subpatcher. The maxforge-only signal modifier marks signal ports; the port count determines the parent object's visible shape.

Live HTTP control

Install the complete Max package and create one maxforge object. It starts a service using Max's own Node runtime. All objects in that Max process share the service. No extra Node installation, MCP setup, controller patch, or manual port selection is needed.

Optional CLI clientSHELL
maxforge live services
maxforge live tools
maxforge live call maxforge_list_patches '{}'

Discovery records live at ~/.maxforge/services/<Max PID>.json. Use its loopback port and bearer token to read GET /v1/tools or call POST /v1/tools/<name> with JSON arguments. The service survives patch and agent disconnects and exits with Max. Browser requests and remote connections are not supported.

If desired DSL uses a project external or abstraction, add an absolute @config path to the first object's text. After catalog edits, call maxforge_reload_catalog and verify the new digest with maxforge_catalog; restarting the service is unnecessary. All objects share the first configuration; conflicting configurations are rejected.

Put a stable project.id in that config when managed state and ordered edit evidence must survive Max restarts. Maxforge stores atomic state and a separate append-only NDJSON journal under ~/.maxforge/projects/<project.id>/. Without a project ID, persistent edit history is disabled rather than merged into a shared default cache.

Saved paths remain locators. When a path warning is ambiguous, inspect the append-only identity ledger instead of guessing. Explicit rekey, merge, and logical forget decisions affect historical lookup only; they never rewrite live Max routing, cross scopes, or claim physical erasure.

When the human explicitly requests deletion of retained edit evidence, close every Max client and use maxforge_erase_project_history with the exact project ID and confirmation phrase. It deletes maxforge-owned history chunks and the identity ledger and clears retained HTTP observations. It does not delete Max/DSL/config files or desired-state cache, and it cannot guarantee secure overwrite on SSDs, backups, or filesystem snapshots.

Persistent state and history are single-writer and owned by the Max service. A project writer lease prevents another Max instance from opening the same persistent project. Without a config, desired-state persistence is scoped to the current Max PID; retain complete DSL across Max restarts.

Replace the whole Max package and restart Max for upgrades. Native and bundled service versions must match. Agents use the service's installed action schemas; no separately versioned agent server is required.

Do not apply blind

Use this sequence for every unfamiliar or ambiguous live session:

  1. 01
    Use maxforge_help only when the compact receipt contract is unavailable, unfamiliar, or in recovery; do not add a redundant call to every normal edit.
  2. 02
    Call maxforge_status when process or connection state is uncertain.
  3. 03
    Before using a custom object, call maxforge_catalog and confirm the loaded definition.
  4. 04
    Call maxforge_list_patches; select an exact patcherId and scope.
  5. 05
    Call maxforge_inspect_patch. Titles are display metadata, not identities.
  6. 06
    When recent order matters, call maxforge_get_live_edit_history. Check supported, persistence, droppedEvents, session identity, and comparisonBasis; this is structural evidence, not undo history.
  7. 07
    If live edits exist, call maxforge_review_live_changes. Its default summary omits the snapshot, raw rows, duplicate signals, and full DSL; request detail: "full" only for exact evidence.
  8. 08
    Adopt an accepted current baseline with the exact reviewed token, or prepare a concrete next state with manualChanges: "merge".
  9. 09
    Call maxforge_prepare_change with complete DSL once, or use retained baseSourceRef plus small half-open line edits. Review counts, every destructive operation, replacements, warnings, and conflicts.
  10. 10
    Call maxforge_apply_prepared_change with only the one-time receiptId. Verify the revision, retain sourceRef, and never retry a consumed receipt.
Desired state, not commands

desiredDsl is a complete desired-state declaration for the owned scope. Omitting an existing managed object requests its deletion; it is not an imperative “add these boxes” fragment.

Tool reference

ToolResponsibility
maxforge_helpAgent-facing workflow, setup, recovery, and safety instructions.
maxforge_statusServer, target, revision, baseline, and effective catalog state.
maxforge_catalogRead loaded built-in/custom object metadata and its deterministic digest; not a Max runtime probe.
maxforge_reload_catalogAtomically reload configured catalog files without restarting HTTP or dropping Max registrations.
maxforge_list_patchesList exact registered patcherId/scope targets.
maxforge_create_patchCreate a new unsaved top-level patch through one controller-capable external.
maxforge_open_patchOpen an existing Max-host .maxpat, inject one bridge object, and wait for registration.
maxforge_save_patchSave or save-as explicitly; apply never saves automatically and overwrite is opt-in.
maxforge_close_patchClose a registered patch; dirty-state discard is explicit and opt-in.
maxforge_inspect_patchRead boxes, patch cords, nested paths, ownership, and changes without screenshots.
maxforge_get_live_edit_historyRead bounded, ordered 75 ms structural observations. Arrival time is not an edit timestamp; check drop and comparison metadata. This is not Max undo history or proof of intent.
maxforge_get_patch_history_identityInspect canonical project history identity, aliases, explicit decisions, and logical-forget state without requiring the old patch to be connected.
maxforge_resolve_patch_history_identityAfter human confirmation and source closure, append a rekey, merge, or logical-forget decision. It does not rewrite live routing or erase original evidence.
maxforge_erase_project_historyAfter explicit human confirmation and full Max disconnection, delete retained edit-history chunks and the identity ledger and clear their in-memory copy. Secure overwrite and deletion of project source/state files are out of scope.
maxforge_review_live_changesReturn compact structural evidence and edit clusters by default; full snapshot/raw rows/proposed DSL are opt-in.
maxforge_adopt_live_changesAccept a reviewed managed graph with an exact token, advance revision without replay, and return a retained source reference.
maxforge_prepare_changeCompile or three-way merge complete desired state, retain the bulk plan, and return a compact one-time receipt plus destructive summary.
maxforge_apply_prepared_changeConsume a receipt, apply without DSL retransmission/recompilation, acknowledge, verify, and retain source state.
maxforge_get_working_sourceRead source metadata, bounded matching snippets, or explicitly requested full retained DSL.

Live edit history is bounded and collapses edits inside one debounce window. Project-scoped NDJSON survives Max restart; reconnect creates a new session baseline instead of comparing across sessions. Saved paths are locators, not patch identities. Selection and gesture boundaries remain unavailable. Edit clusters are structural correlations, not recorded gestures or semantic conclusions. Follow each cluster's changeIndexes to the exact raw before/after changes. Use clarificationRecommendedFor to focus reasoning, but ask a human only when unresolved interpretations would change the next mutation.

maxforge

The external owns WebSocket transport, stable patch registration, preflight validation, live structural inspection, and Max SDK mutation. It does not need node.script, thispatcher, a separate WebSocket object, routers, or bootstrap patch cords.

maxforge
AttributeMeaning
@patcher_idStable HTTP routing identity. Do not derive it from the window title.
@scopeExact managed ownership namespace.
@configOptional absolute catalog/project configuration path, shared by this Max process.
@revision_statePersisted optimistic-concurrency revision.

Managed scripting names follow maxforge_<scope>_obj_<dsl-name>. The external must not modify boxes outside that exact namespace.

Any registered object can service maxforge_create_patch. Closing the original patch does not stop other patches or the service.

Ambiguous state requires inspection

SituationRequired action
Apply timed outDo not retry. Call maxforge_help with recovery, inspect the live patch, and determine whether Max applied the revision.
Service failureRestart Max, then status → list → inspect. Provide exact complete currentDsl if the previous baseline was not persisted.
Max or the patch restartedWait for registration, then status → list → inspect before compiling another plan.
Managed manual edit detectedReview first. Adopt the accepted current baseline with its exact token and retain returned sourceRef, or prepare a concrete next state with manualChanges: "merge".
Unmanaged manual edit detectedReview it. Unmanaged-only edits are reported but do not block managed mutation.

Install operating instructions

Skills do not install the npm package or Max external. They give compatible agents repository-specific instructions.

npx skills add bbb-max-externals/maxforge --list
npx skills add bbb-max-externals/maxforge --skill maxforge
npx skills add bbb-max-externals/maxforge --skill maxforge-live

Use maxforge for offline authoring and compilation. Use maxforge-live for target selection, plan review, acknowledgement, inspection, and recovery discipline.

Current limits and security boundary

  • Live synchronization is experimental protocol version 1.
  • Project catalog entries describe compiler serialization and ports. They do not prove that the external binary, abstraction search path, architecture, or dependencies exist on the Max host.
  • A plan is fully validated before mutation, but arbitrary Max SDK mutation has no implemented rollback after a runtime failure.
  • Inspection is structural. It does not serialize arbitrary object attributes, DSP values, UI values, patchline colors, hidden state, or line midpoints.
  • Reverse compilation preserves structure and explicit positions; it does not reproduce the exact original DSL source text.
  • HTTP and WebSocket listen only on loopback with a generated token. Tokens are not saved in Max patches. Browser origins are rejected; LAN mode is not available.
  • The ownership marker is the reserved scripting-name namespace. The native consumer treats such boxes as owned, but HTTP reconciliation rejects newly introduced reserved identities because inspection lacks enough metadata for safe adoption.
  • Reconciliation preserves observed edits to existing managed identities. Arbitrary object attributes omitted from structural inspection cannot be merged.
  • Moving a managed box to another patcher path is not merged as a move; express the intended delete/add in complete DSL and resolve the conflict explicitly.
  • HTTP apply plans carry the inspected structure token. If a box or cord changes before native mutation, maxforge rejects the stale plan.
  • Only CI-produced versioned macOS release archives are Developer ID signed and notarized. Local builds remain ad-hoc signed.

Detailed references

This page is the operational guide. Use the repository documents below for exact schemas and contracts.