DocumentationCustomization
Documentation

Customization

Each manifest's customization.capabilities is the complete list of what may change on an instance — in the browser, through the SDK, without Blender. Anything else is refused with a structured error.

Every asset and every refusal, with the alternative, is listed on capabilities and limits.

Capability levels

LevelWhatBlender
L1Load and place the published GLB as-is (22 assets)not needed
L2Declared runtime capabilities: recolour, swatches, uniform scale, visibility toggles, part swaps (8 assets)not needed
L3Scene assembly with connector snapping (scene 1.1.0), clipsnot needed
L4Geometry authoring (new shapes, storeys, roof pitch)outside this kit (no .blend sources included)

Assets with capabilities

AssetCapabilities
stylized-town/bench-01@0.4.0wood-color (material-color), size-scale (uniform-scale), armrests (node-visibility)
stylized-town/cafe-01@0.1.0front-door (modular-swap), awning (node-visibility), terrace (node-visibility), wall-color (material-color)
stylized-town/house-01@0.2.0front-door (modular-swap), window-style (modular-swap), roof (modular-swap), chimney (node-visibility), wall-color (material-color), roof-color (material-color)
stylized-town/house-02@0.1.0front-door (modular-swap), window-style (modular-swap), roof (modular-swap), wall-color (material-color), roof-color (material-color)
stylized-town/market-stall-01@0.1.0canopy-color (material-color)
stylized-town/shop-01@0.2.0front-door (modular-swap), awning (node-visibility), wall-color (material-color)
stylized-town/street-lamp-01@0.3.0light (node-visibility)
stylized-town/townhall-01@0.1.0flag (node-visibility), wall-color (material-color)

Inspect, write a config, validate

Run these in a project with the kit installed (after the quickstart). The example uses stylized-town/house-01@0.2.0, the asset with the most capabilities.

Terminalcopy as is
npx agent-ready-assets inspect stylized-town/house-01@0.2.0 --capabilities --connectors
my-config.jsoncopy as is
{
  "schema_version": "1.0.0",
  "asset": "stylized-town/house-01@0.2.0",
  "values": {
    "front-door": "door-02",
    "window-style": "window-02",
    "roof": "roof-hip-01",
    "chimney": false,
    "wall-color": "paving",
    "roof-color": "curb-stone"
  }
}

Configs are absolute (every capability not listed uses its default) and all-or-nothing: one invalid value rejects the whole config.

Terminalcopy as is
npx agent-ready-assets customize stylized-town/house-01@0.2.0 --config my-config.json --output src/generated/house-01.customization.json

The output file is the validated config in canonical form, not a modified GLB: nothing imports it for you. Import it in your code and pass its values to library.instantiate (below), or write the same values into a scene's customize block. verify-project checks every config file it finds in the project.

Check a config without writing anything

--dry-run (SDK 0.5.1) validates the config exactly like a real run and writes nothing, so --output is optional and may point anywhere. A target a real run would refuse — outside the project, or an existing file without --force — is named in warnings instead:

Terminalcopy as is
npx agent-ready-assets customize stylized-town/house-01@0.2.0 --config my-config.json --output /tmp/house-01.customization.json --dry-run

Without --dry-run the same command is refused (exit 1, PATH_OUTSIDE_PROJECT, nothing written): real runs write only inside the project.

Use it in code

Three.js
const library = await AssetLibrary.fromKit('/assets/stylized-town/kit.json');
const config = {"schema_version":"1.0.0","asset":"stylized-town/house-01@0.2.0","values":{"front-door":"door-02","window-style":"window-02","roof":"roof-hip-01","chimney":false,"wall-color":"paving","roof-color":"curb-stone"}};
const instance = await library.instantiate(config.asset, config.values); // all-or-nothing: throws CustomizationError on invalid input
scene.add(instance.object);
instance.parts;       // parts mounted by modular-swap: [{ capability, choice, slot, ref, plug, asset }] (default choices stay baked)
instance.hiddenNodes; // optional parts hidden on this instance only
// the mounted stylized-town/door-02@0.1.0 brings the clip "open": play it on that part instance (import { playClip } from '@agent-ready-assets/runtime-three')
const part = instance.parts.find((p) => p.slot === 'anchor-door-main')?.asset;
const player = part ? playClip(part, "open", { loop: 'pingpong' }) : null; // per frame: player?.update(deltaSeconds)
// later: instance.release(); // disposes its own materials and releases the mounted parts
React Three Fiber
// customize accepts only declared capabilities (swap choice ids, booleans for toggles, swatch names for palette colours);
// anything else calls onError with a structured error. Swaps load their part asynchronously; unmounting releases it.
<Asset assetId="stylized-town/house-01@0.2.0" customize={{"front-door":"door-02","window-style":"window-02","roof":"roof-hip-01","chimney":false,"wall-color":"paving","roof-color":"curb-stone"}} clip={{ name: "open", slot: 'anchor-door-main', loop: 'pingpong' }} onError={(e) => console.error(e)} />

Re-customize an instance of an assembled scene

await town.customize(instanceId, values) (SDK 0.5.1) replaces one instance of an assembleScene result with a new instance of the same asset. Values are absolute, like a config. The whole scene is validated again first (values, swap parts, snaps, overlaps); on any error nothing changes. The optimization, if any, is rebuilt. In React Three Fiber, change that <Asset>'s customize prop instead.

Three.js — town-customized, the scene the integration guide's entry file assembles
const town = await library.assembleScene(sceneJson, { optimize: true });
// "house-main-b" is a stylized-town/house-01@0.2.0 instance of town-customized
await town.customize('house-main-b', {"front-door":"door-02","window-style":"window-02","roof":"roof-hip-01","chimney":false,"wall-color":"paving","roof-color":"curb-stone"});
// CustomizationError on any invalid value (nothing changes); RELEASED_SOURCE after town.release(); INSTANCE_UNKNOWN for an id not in the scene

Variants of one base asset

Buildings that look different but share one model are variants of one base asset: place the same asset several times and give each instance its own declared choices — part swaps (front-door, window-style, roof) and palette swatches (wall-color). Two different assets whose defaults happen to differ are not variants: they are two models.

  • Another footprint or shape → another asset. The kit's other buildings: stylized-town/cafe-01@0.1.0 (4 × 8 m lot), stylized-town/house-02@0.1.0 (4 × 7.2 m lot), stylized-town/shop-01@0.2.0 (4 × 6.4 m lot), stylized-town/townhall-01@0.1.0 (8 × 8 m lot). stylized-town/house-02@0.1.0 declares the same part swaps but is another model — 4 × 7.2 m lot and 6.02 m high, where stylized-town/house-01@0.2.0 has a 4 × 6.2 m lot and is 8.61 m high: one of each is two models, not two variants of house-01.
  • Variants of one model → its part swaps and swatches. Every value must be one of the asset's declared choices; check them with npx agent-ready-assets inspect stylized-town/house-01@0.2.0 --capabilities (or the MCP tool get_customization_capabilities).
  • A default choice is baked into the GLB (nothing is mounted); any other choice mounts its pinned part on that instance only. Each instance owns its parts and colours; the other instances and the cached GLB never change.
Declared values of the two variants of stylized-town/house-01@0.2.0
Capabilityhouse-01-ahouse-01-bDeclared choices
front-door
Part swap
door-02door-01 (default)door-01 → stylized-town/door-01@0.1.0, door-02 → stylized-town/door-02@0.1.0
window-style
Part swap
window-01 (default)window-02window-01 → stylized-town/window-01@0.1.0, window-02 → stylized-town/window-02@0.1.0
roof
Part swap
roof-hip-01roof-gable-01 (default)roof-gable-01 → stylized-town/roof-gable-01@0.1.0, roof-hip-01 → stylized-town/roof-hip-01@0.1.0
wall-color
Recolour
pavingleaf ivory, paving, leaf

Both variants differ from the asset's defaults — house-01-a in front-door, roof, wall-color, house-01-b in window-style, wall-color — and from each other in every varied capability, so each renders differently from the plain asset and from the other. An instance at the defaults is the plain asset, not a variant.

Left at their defaults in this example: chimney, roof-color.

In one scene: the two variants of stylized-town/house-01@0.2.0 snapped by their lot fronts (attach_to) to two joined road tiles, stylized-town/road-straight-01@0.3.0 (Straight road tile (4 m)) — 4 instances of 2 assets, 3 snapped. Save it as src/scenes/variants.scene.json in a project with the kit installed and validate it:

src/scenes/variants.scene.jsoncopy as is
{
  "schema_version": "1.1.0",
  "kit": "stylized-town@0.4.0",
  "instances": [
    {
      "instance_id": "road-straight-1",
      "asset": "stylized-town/road-straight-01@0.3.0",
      "position_m": [
        0,
        0,
        0
      ],
      "rotation_euler_rad": [
        0,
        0,
        0
      ],
      "uniform_scale": 1,
      "customize": {}
    },
    {
      "instance_id": "road-straight-2",
      "asset": "stylized-town/road-straight-01@0.3.0",
      "uniform_scale": 1,
      "customize": {},
      "attach_to": {
        "instance_id": "road-straight-1",
        "anchor_id": "anchor-road-east",
        "connector": "anchor-road-west"
      }
    },
    {
      "instance_id": "house-01-a",
      "asset": "stylized-town/house-01@0.2.0",
      "uniform_scale": 1,
      "customize": {
        "front-door": "door-02",
        "window-style": "window-01",
        "roof": "roof-hip-01",
        "wall-color": "paving"
      },
      "attach_to": {
        "instance_id": "road-straight-1",
        "anchor_id": "anchor-side-south",
        "connector": "anchor-lot-front"
      }
    },
    {
      "instance_id": "house-01-b",
      "asset": "stylized-town/house-01@0.2.0",
      "uniform_scale": 1,
      "customize": {
        "front-door": "door-01",
        "window-style": "window-02",
        "roof": "roof-gable-01",
        "wall-color": "leaf"
      },
      "attach_to": {
        "instance_id": "road-straight-2",
        "anchor_id": "anchor-side-south",
        "connector": "anchor-lot-front"
      }
    }
  ]
}
Terminalcopy as is
npx agent-ready-assets scene validate src/scenes/variants.scene.json --resolve

Prints every resolved transform with the measured mate. Then npx agent-ready-assets verify-project --browser chromium renders every scene file of the project, this one included (LOCALLY_VALIDATED).

Render it (Three.js, src/main.ts)
import variants from './scenes/variants.scene.json'; // a Vite JSON import

const town = await library.assembleScene(variants, { optimize: true }); // validated and snapped, all-or-nothing
scene.add(town.group);

Unsupported requests are refused

An agent asked for something the manifest does not declare gets a structured error with the allowed alternatives — never an approximation. Every refusal with its documented alternative: capabilities and limits.

storeys.jsoncopy as is
{
  "schema_version": "1.0.0",
  "asset": "stylized-town/house-01@0.2.0",
  "values": {
    "storeys": 3
  }
}
Terminalcopy as is
npx agent-ready-assets customize stylized-town/house-01@0.2.0 --config storeys.json --output src/generated/storeys.json
RequestResult
{ "kind": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
{ "width-m": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
{ "depth-m": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
{ "body-z-m": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
{ "plinth-height-m": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
{ "storey-height-m": ... }UNSUPPORTED_OPERATION: producer recipe parameter, not a Blender-free capability (needs L4 authoring)
recolor or edit a baked textureUNSUPPORTED_OPERATION: only declared material colours can change
add, remove or reshape geometry (e.g. extra floors or storeys, a taller tower, a roof pitch)UNSUPPORTED_OPERATION: only the declared parts can be swapped or hidden; no geometry operation is offered
a part that is not in the swap allowlist (or the part ref instead of the choice id)INCOMPATIBLE_PART/PART_NOT_ALLOWED: only the declared choices exist; the hint lists them
a free #RRGGBB colour on a palette-swatch capabilityINVALID_PARAMETER/NOT_IN_CHOICES: use one of the swatch names
a visibility toggle given as a string ("false")INVALID_PARAMETER/WRONG_TYPE: toggles are booleans
hiding a part that is not declared optional (walls, roof, a slot part)UNSUPPORTED_OPERATION/UNKNOWN_CAPABILITY: only declared node-visibility capabilities exist
a swap part that is not installed in your kitINCOMPATIBLE_PART/PART_NOT_IN_KIT: install the complete kit (or `add` the host, which installs its pinned parts)
snapping a part instance into a host slot in scene.jsonVALIDATION_FAILED/SNAP_OCCUPIED: slots change only through the modular-swap capability

Evidence labels

  • A tested preset (its config_sha256 is in the asset's quality report) was rendered and measured before publication.
  • Any other legal configuration is SUPPORTED_BUT_UNTESTED until your verify-project --browser chromium run marks it LOCALLY_VALIDATED.
  • PRESET_VERIFIED needs a released package with a recorded approval of that preset by a person; the asset's quality report shows whether it has one.