system-composer

$npx mdskill add matlab/agent-skills-playground/system-composer

Author reusable System Composer architecture models via MATLAB API scripts.

  • Builds architecture models programmatically using systemcomposer API functions.
  • Depends on System Composer toolbox and MATLAB's architecture-modeling API.
  • Triggers on API usage patterns like createModel, addComponent, connect, and interface dictionaries.
  • Outputs idempotent MATLAB scripts that create or debug System Composer models.

SKILL.md

.github/skills/system-composerView on GitHub ↗
---
name: system-composer
description: Use this skill when authoring reusable, idempotent MATLAB scripts that build System Composer architecture models via the architecture-modeling API — `systemcomposer.createModel`, `addComponent`, `addPort`, `setInterface`, `connect(srcPort, dstPort)`, interface dictionaries (.sldd) with `addInterface`/`addElement`, profiles/stereotypes with `Profile.createProfile` and `addStereotype`, or `systemcomposer.allocation.createAllocationSet`. Also trigger when debugging these APIs (connections that don't appear, interfaces that don't resolve, profile save errors, `createAllocationSet` signature-mismatch errors). Do NOT trigger for ad-hoc structural edits to an already-built model (adding one SubSystem, rewiring a port) — use `building-simulink-models` with `model_edit` for that.
license: MathWorks BSD-3-Clause (see LICENSE)
metadata:
  author: MathWorks
  version: "1.0"
---

# MATLAB System Composer — Programmatic Authoring Guide

System Composer lets you model multi-domain architectures in MATLAB. This skill captures the
correct API patterns, common gotchas, and a proven script structure for building models reliably.

---

## When to use this skill vs. `building-simulink-models` (SATK)

Both skills can edit System Composer `.slx` files. They work at **different API layers** — don't mix them in one script.

| Concern | This skill (architecture-modeling API) | `building-simulink-models` with `model_edit` (block-diagram API) |
|---|---|---|
| Component creation | `addComponent(arch, "Name")` — returns `systemcomposer.Component` | `add_block` with `type: "SubSystem"` — returns a `blk_id` |
| Ports | `addPort(arch, "Name", "in", iface)` — typed, interface-aware | Bus Element blocks (`In Bus Element` / `Out Bus Element`) inside the SubSystem |
| Connections | `connect(srcPort, dstPort)` — port objects | `{"op": "connect", "target": "blk_X.y1 -> blk_Y.PortName"}` |
| Interface dictionaries (`.sldd`) | First-class (`createDictionary`, `addInterface`, `setInterface`) | Not addressed |
| Profiles / stereotypes | First-class (`Profile.createProfile`, `addStereotype`, `applyStereotype`) | Not addressed |
| Allocation sets (`.mldatx`) | First-class (`systemcomposer.allocation.createAllocationSet`) | Not addressed |
| Auto-layout | **Call `Simulink.BlockDiagram.arrangeSystem` explicitly** before `save` — programmatic adds all land at (0,0) | `model_edit` runs autolayout automatically; its guardrail forbids manual `arrangeSystem` |

**Use this skill when:**
- Writing an idempotent `buildMyModel.m` / `buildMyArchitecture.m` script that will be re-run from scratch
- The architecture uses interface dictionaries, stereotypes/profiles, or allocation sets — `model_edit` has no primitives for any of these
- Debugging SC-specific API failures (CST `connect` shadow, composite `ArchitecturePort` errors, `dict.save` + re-fetch, `profile.save` path, `createAllocationSet` signature mismatch)

**Defer to `building-simulink-models` with `model_edit` when:**
- Making a one-off structural change to an already-built SC model (add one SubSystem, rewire one port, tweak a parameter)
- The user just wants "add a component called X" and the model has no interface dictionary / profile / allocation set the change needs to stay consistent with
- The MBSE workflow in `mbse-workflow` is not involved

**Do not mix in one script.** `model_edit` adds components via `add_block` with `type: "SubSystem"`; this skill adds them via `addComponent`. The two produce different object types and the architecture-modeling APIs in this skill (`setInterface`, `applyStereotype`, `addPort`) may not work on SubSystem-block-created components. Pick one layer per script.

---

## Recommended Script Structure

Keep profile creation in the same script as the architecture — add it at the end,
after the model and connections are built:

```
buildMySystemModel.m    ← architecture + interface dictionary + profile/stereotypes
```

This keeps both artifacts in sync on every rebuild, and avoids the "profile already
applied" uniqueness error that occurs when a separate profile script re-applies a
profile to an already-profiled model.

The script is idempotent: it deletes and recreates all artifacts on every run.

---

## Phase 1+2: Architecture Model + Interface Dictionary

### Skeleton

See [`code/buildMySystemModel.m`](code/buildMySystemModel.m) for the full parameterized function:

```
buildMySystemModel(modelName, dictFile, archDir)
```

---

## Phase 3: Profile & Stereotypes

See [`code/buildMySystemProfile.m`](code/buildMySystemProfile.m) for the full parameterized function:

```
buildMySystemProfile(profileName, modelName, archDir)
```

---

## Critical API Gotchas

These will silently fail or throw cryptic errors without warning:

| What you want | Correct API | Wrong / common mistake |
|---|---|---|
| Connect two component ports | `connect(srcPort, dstPort)` | `connect(arch, srcPort, dstPort)` — silently fails; dispatches to Control System Toolbox |
| Wire a composite boundary port to a sub-component port | `connect(boundaryArchPort, subComp.getPort("Name"))` — boundary is the saved `ArchitecturePort` ref; sub-component is its `ComponentPort` via `getPort` | Connecting two `ArchitecturePort`s across the boundary, or using the boundary's `ComponentPort` — both throw "incompatible directions" |
| Assign interface to port | `port.setInterface(iface)` | `port.Interface = iface` — read-only property error |
| Add interface element | `addElement(iface, "Name", Type="double")` | `Type="MyValueTypeName"` — value type names resolve to `Simulink.ValueType` objects which the bus compiler cannot use; always use a Simulink base type directly |
| Create model | `systemcomposer.createModel(name)` | `systemcomposer.createModel(name, true)` — invalid 2nd arg |
| Set string stereotype property | `setProperty(comp, path, '"value"')` | `setProperty(comp, path, 'value')` — evaluates as MATLAB variable |
| Set string default in addProperty | `DefaultValue='"standard"'` | `DefaultValue="standard"` — evaluates, throws error |
| Apply stereotype property | `setProperty(comp, ...)` | `setPropertyValue(comp, ...)` — wrong function name |
| Close all profiles | `systemcomposer.profile.Profile.closeAll()` | `...closeAll("-discard")` — too many arguments |
| Create profile | `createProfile(name)` | `createProfile(name, "file.xml")` — invalid 2nd arg |
| Look up a component by path | Keep component vars when adding, or use `resolveComponent(arch, 'Parent/Child')` helper | `arch.lookup('Path', ...)` — does not exist |
| Create allocation set | `createAllocationSet(name, srcModelName, dstModelName)` — model **names** | `createAllocationSet(name, srcModelObj, dstModelObj)` — model objects fail on R2025b with unhelpful AllocationAppCatalog signature error |

---

## Why `connect(srcPort, dstPort)` Must Have No Architecture Argument

`connect` is shadowed by Control System Toolbox's `connect.m`. When you write
`connect(arch, srcPort, dstPort)`, MATLAB dispatches to the CST version (which connects
LTI models) instead of System Composer — it returns an empty 0×0 Connector silently.

The two-argument form `connect(srcPort, dstPort)` dispatches correctly via the port object's
class method. Always use this form for explicit port-to-port connections.

For component-to-component auto-wiring by matching port names, the form
`connect(arch, [srcComp,...], [dstComp,...])` is also safe since `arch` dispatches correctly.

---

## Port Multiplicity: Fan-Out Works, Fan-In Needs Separate Ports

A single **output port** can be connected to any number of input ports — just call
`connect(out, in1); connect(out, in2); ...`. This is clean 1→N fan-out; use it for
broadcast buses (e.g. `Supervisory.Schedule` feeding every production unit).

A single **input port** cannot receive connections from multiple sources. SC is a
structural modelling tool with no merge semantics — attempting to hook two
outputs into one input either errors or silently fails. Give the destination
component **separate named input ports** for each source (e.g.
`Status_Cook`, `Status_Pack`, `Status_Load`) and wire 1:1. This pattern is
verbose but unambiguous and matches how an MBSE diagram will render for review.

---

## Composite Components: Mix ArchitecturePort and ComponentPort for Internal Wiring

When a component has sub-components (a composite), its boundary ports have two views:

| View | Accessed via | Class | Used for |
|---|---|---|---|
| External | `comp.Ports` / `comp.getPort(name)` | `ComponentPort` | Connections in the **parent** arch |
| Internal | return value of `addPort(comp.Architecture, ...)` | `ArchitecturePort` | Connections **inside** the composite, to sub-component ports |

A boundary "in" port acts as a *source* from inside the composite's sub-architecture (it delivers data to the sub-components), but its external `ComponentPort` still reports direction `in`. Using the wrong view throws:

```
Unable to connect ports because they have incompatible directions.
```

### The exact rule (verified on R2025b)

Internal connections inside a composite require **specific port classes on each side** — verified empirically; mixing them wrong fails every time:

| Connection inside the composite | Source port | Destination port | `connect` call |
|---|---|---|---|
| Boundary → sub-component | Saved boundary `ArchitecturePort` | Sub-component `ComponentPort` (via `getPort`) | `connect(boundaryRef, sub.getPort("Name"))` |
| Sub-component → boundary | Sub-component `ComponentPort` (via `getPort`) | Saved boundary `ArchitecturePort` | `connect(sub.getPort("Name"), boundaryRef)` |
| Sub-component → sub-component | `ComponentPort` (via `getPort`) | `ComponentPort` (via `getPort`) | `connect(a.getPort("Out"), b.getPort("In"))` — same as top-level |

**Two `ArchitecturePort`s on opposite sides of the boundary fail** with "incompatible directions." That means the `motorPow` pattern — passing the return value of `addPort(sub.Architecture, ...)` to `connect` alongside a boundary `ArchitecturePort` — does **not** work. Use `sub.getPort(name)` instead for the sub-component side.

### Pattern

```matlab
function port = addTypedPort(comp, name, direction, iface)
    port = addPort(comp.Architecture, name, direction);
    port.setInterface(iface);
end

% Composite creation — keep the boundary refs
conv = addComponent(arch, "ConveyorSystem");
convPowerIn = addTypedPort(conv, "PowerIn", "in", ifPower);   % ArchitecturePort
convDiagOut = addTypedPort(conv, "DiagOut", "out", ifDiag);   % ArchitecturePort

% Sub-components — no need to save their port refs
motor = addComponent(conv.Architecture, "Motor");
addTypedPort(motor, "PowerIn",  "in",  ifPower);
addTypedPort(motor, "DiagOut", "out", ifDiag);

% Boundary -> sub: saved ArchitecturePort + sub's ComponentPort
connect(convPowerIn, motor.getPort("PowerIn"));        % ✓ correct

% Sub -> boundary: sub's ComponentPort + saved ArchitecturePort
connect(motor.getPort("DiagOut"), convDiagOut);        % ✓ correct

% DO NOT: two ArchitecturePorts across the boundary
% motorPow = addTypedPort(motor, "PowerIn", "in", ifPower);
% connect(convPowerIn, motorPow);                      % ✗ incompatible directions
```

External connections from the parent arch continue to use `conv.getPort(name)` (ComponentPort) as normal, the same as top-level connections between non-composite components.

### Why `addTypedPort(comp, ...)` takes the Component (not its Architecture)

The helper called with a Component does `addPort(comp.Architecture, ...)` internally, so either calling convention works. Passing the Component is slightly cleaner for the common case and matches how non-composite components add ports. For a composite, the *return value* is still the internal `ArchitecturePort` — save it when you'll need it for boundary-to-sub wiring.

---

## Variant Components

A **variant component** is a sibling concept to a composite: one wrapper with N alternative internal architectures (*choices*), only one of which is active at a time. Use this for architecture-options studies where you want to compare candidate topologies without duplicating the whole model file.

See [`code/buildMyVariantComposite.m`](code/buildMyVariantComposite.m) for a working build template and [`code/tradeStudy.m`](code/tradeStudy.m) for the driver that enumerates variants and emits a markdown comparison report.

### Canonical build sequence

```matlab
% 1. Build a regular composite with its boundary ports + the baseline content
comp = addComponent(arch, 'CookingLine');
addPort(comp.Architecture, 'RecipeData', 'in');
% ... add sub-components for the baseline ...

% 2. Convert to a variant wrapper. The baseline content becomes the first
%    auto-created choice (named the same as the wrapper).
vc = comp.makeVariant();

% 3. Re-fetch the wrapper -- the old `comp` reference is STALE (see below).
cookingLine = arch.getComponent('CookingLine');

% 4. Rename the auto-choice AND sync its variant condition to match.
ch = vc.getChoices();
ch(1).Name = 'V0_Baseline';
vc.setCondition(ch(1), 'V0_Baseline');

% 5. Add additional choices; each choice is a regular Component with its own
%    Architecture. Wire internal content inside each choice.
v1 = vc.addChoice({'V1_Parallel'});
addPort(v1.Architecture, 'RecipeData', 'in');
addComponent(v1.Architecture, 'ParallelA');
addComponent(v1.Architecture, 'ParallelB');

% 6. Choose the default active variant.
vc.setActiveChoice('V0_Baseline');
```

### Gotcha — the original `Component` reference is stale after `makeVariant`

Once `makeVariant` returns, port lookups on the original variable fail with `"not in same architecture scope"`. External connections placed *before* `makeVariant` survive the conversion (verified empirically), but any subsequent `comp.getPort(...)` or `connect(...)` calls must use a fresh reference:

```matlab
vc = comp.makeVariant();
freshComp = arch.getComponent('CookingLine');   % returns VariantComponent

% Wrong -- stale scope:
connect(other.getPort('Out'), comp.getPort('RecipeData'));   % ✗

% Right:
connect(other.getPort('Out'), freshComp.getPort('RecipeData'));   % ✓
```

### Gotcha — rename + `setCondition` must stay in sync

`ch.Name = 'V0_Baseline'` does *not* update the variant condition. `setActiveChoice` matches the given string against conditions, so without re-syncing:

```matlab
ch(1).Name = 'V0_Baseline';
vc.setActiveChoice('V0_Baseline');
%   Error: Setting active choice for variant block '...' with variant control
%          'V0_Baseline' is not supported.
```

Fix by calling `vc.setCondition(ch(1), 'V0_Baseline')` immediately after the rename.

### Gotcha — `applyStereotype` on the wrapper throws

```
Unable to apply stereotype 'Profile.Stereotype' on variant architecture '.../Wrapper'.
```

Apply the stereotype to each *choice* instead. Numeric property values set on the active choice propagate to the wrapper's instance at `instantiate` time, so the rollup callback sees the right value when it visits the wrapper. This is the mechanism that makes per-variant rollup estimates work.

### Gotcha — **string** stereotype properties do NOT propagate choice → wrapper instance

Verified empirically on R2025b. A `Type="double"` property set on the active choice shows up on the wrapper's instance (`instance.hasValue(...) == true`, `getValue(...)` returns the choice's number). A `Type="string"` property shows up as `hasValue == false` on the wrapper's instance.

This matters when you want the rollup callback to branch on variant-specific behavior (e.g. MIN aggregation for serial, SUM for parallel). **Encode the flag as a number, not a string:**

```matlab
% Works across the choice → wrapper instance boundary:
addProperty(st, "UseParallelThroughput", Type="double", DefaultValue="0");
setProperty(parallelChoice, stPath + ".UseParallelThroughput", "1");

% Does NOT work:
addProperty(st, "ThroughputAggregation", Type="string", DefaultValue='"MIN"');
setProperty(parallelChoice, stPath + ".ThroughputAggregation", '"SUM"');
%   -> wrapper's instance reports hasValue == false for this property
```

See [`mbse-architecture/references/analysis.md#topology-dependent-rollup`](../mbse-architecture/references/analysis.md#topology-dependent-rollup) for the rollup callback pattern that reads the numeric flag.

### Gotcha — auto-layout skips inactive choices

`comp.Architecture.Components` on a `VariantComponent` returns only the *active* choice's sub-components, so a generic recursive `arrangeComposites` walker that follows `.Architecture` never lays out the inactive variants. Iterate `vc.getChoices()` explicitly:

```matlab
function arrangeComposites(arch, pathPrefix)
    for comp = arch.Components
        subPath = [pathPrefix, '/', comp.Name];
        if isa(comp, 'systemcomposer.arch.VariantComponent')
            Simulink.BlockDiagram.arrangeSystem(subPath);
            for ch = comp.getChoices()
                choicePath = [subPath, '/', ch.Name];
                Simulink.BlockDiagram.arrangeSystem(choicePath);
                arrangeComposites(ch.Architecture, choicePath);
            end
        elseif ~isempty(comp.Architecture) && ~isempty(comp.Architecture.Components)
            Simulink.BlockDiagram.arrangeSystem(subPath);
            arrangeComposites(comp.Architecture, subPath);
        end
    end
end
```

Each choice's Simulink path is `Model/Wrapper/ChoiceName` — regular `arrangeSystem` works on them.

### Minor — `updatePortsFromChoices` requires the `'Mode'` kwarg

```matlab
vc.updatePortsFromChoices();                    % error: 'Mode' must be specified
vc.updatePortsFromChoices('Mode','addPorts');   % ✓ adds missing wrapper ports from choices
```

Use this when choices define their own boundary ports and you want the wrapper to pick them up. If instead you add ports on the wrapper's `Architecture` *before* `makeVariant` and mirror them on each choice manually, you don't need this call.

---

## Sequence Diagrams

A **sequence diagram** (SC calls these "Interactions") is a behavioral view layered on top of an architecture model. It shows how components collaborate over time for a specific scenario: which messages pass between them, in what order, under what guards. Unlike a Mermaid/PlantUML sequence diagram, every SC message is **bound to real ports on real components** — rename a port upstream and the sequence-diagram build errors, which is the feature not a bug.

See [`code/buildMySequenceDiagram.m`](code/buildMySequenceDiagram.m) for a working template.

### Mental model

```
Model
 └── Interaction                      (one per scenario)
      ├── Lifelines                   (one per participant, bound to a Component)
      └── RootFragment
           └── Operands(1)            (pre-created default operand; messages live here)
                ├── addMessage(...)
                └── addFragment('Alt' | 'Loop' | 'Opt' | 'Par')
                     └── Operands(1..N) → more messages, recursively
```

Messages hang off *operands*, not directly off fragments or the interaction. `Interaction.RootFragment.Operands(1)` is where the straight-line message sequence goes. `Alt` creates a fragment with two operands; each has its own `Guard` string and its own `addMessage`.

### Canonical build sequence

```matlab
model = systemcomposer.openModel('MyModel');
destroyInteractionIfPresent(model, 'MyScenario');   % idempotent rebuild
diagram = model.addInteraction('MyScenario');

L1 = diagram.addLifeline('MyModel/CompA');          % path OR Component object
L2 = diagram.addLifeline('MyModel/CompB');

op = diagram.RootFragment.Operands(1);
op.addMessage(L1, 'OutPort', L2, 'InPort', 'request');
op.addMessage(L2, 'ReplyOut', L1, 'ReplyIn', 'reply');

save_system('MyModel');                              % interactions live INSIDE the .slx
open(diagram);                                       % show in Sequence Viewer
```

### Gotcha — `addMessage` is 5 arguments, not 3

```matlab
op.addMessage(L1, L2, 'request')                     % ✗ "Function requires 3 more input(s)"
op.addMessage('request', L1, L2, 1)                  % ✗ various complaints
op.addMessage(L1, 'OutPort', L2, 'InPort', 'request')% ✓ (src, srcPort, dst, dstPort, guard)
```

The 2nd and 4th args are **port names that must exist on the components underlying the lifelines**. If you pass a name that doesn't match a port on the source component, you get `"Name must match a port on the component corresponding to the lifeline"`. This ties every message to the structural model and is the main reason to prefer programmatic SC sequence diagrams over free-form Mermaid.

### Gotcha — messages do NOT live on Fragments directly

```matlab
diagram.RootFragment.addMessage(...)                 % ✗ no such method
diagram.RootFragment.Operands(1).addMessage(...)     % ✓
```

Same for `Alt`/`Loop`/`Opt` fragments: get `.Operands(i).addMessage(...)`. `Alt.Operands` has length 2 after `addFragment('Alt')`; set `op1.Guard = "cond1"` / `op2.Guard = "cond2"`.

### Gotcha — idempotent rebuild requires explicit `destroy()`

`model.addInteraction(name)` errors on duplicate name. Always delete the existing one first:

```matlab
function destroyInteractionIfPresent(model, name)
    try, ixns = model.getInteractions(); catch, ixns = []; end
    for i = 1:numel(ixns)
        if strcmp(ixns(i).Name, name), ixns(i).destroy(); return; end
    end
end
```

Put the build step AFTER the architecture-model build step in `buildAll` — a rebuild of the architecture model wipes interactions along with everything else, so the sequence diagram must be re-created each time.

### Guard syntax

Guards accept trigger names, boolean expressions in braces, or both:

```
'cookComplete'                           event name
'{Accepted==1}'                          boolean on interface fields
'rocketDocked{RocketPresent==1}'         event + condition
'rising(sw-1){sw==1}'                    signal transition + condition
```

Use interface-element names (from the dictionary — e.g., `Accepted` on `QCVerdict`) so the guard text stays consistent with the rest of the model.

### Duration constraints

`message.Start` and `message.End` return `MessageEvent` objects that can be passed to `addDurationConstraint`:

```matlab
t0 = msg1.End;
t1 = msg2.End;
diagram.addDurationConstraint(t0, t1, 't < 10sec');   % assertion on the render + runtime check
```

### Persistence + viewing

- Interactions are serialized *inside* the model's `.slx` — `save_system(modelName)` commits them; no separate file to track.
- `open(diagram)` opens the SC Sequence Viewer canvas. `diagram.open()` also works.

### Requirement traceability — see the slreq skill

slreq does not accept an `Interaction` object as a `createLink` source on R2025b, and the struct-based workaround (`domain='linktype_sc_interaction'`) fails to persist when the containing `.slx` already hosts `linktype_rmi_simulink` Implement links. See [`simulink-requirements/SKILL.md`](../simulink-requirements/SKILL.md) — search for "Interaction"; it covers the recommended convention-based trace pattern.

---

## Why dict.save() + Re-fetch Is Required

`systemcomposer.createDictionary()` creates the file but interface objects in memory aren't
fully resolved until after `dict.save()`. If you call `port.setInterface(iface)` before saving,
the model links the port to an unresolvable interface name — connections will silently fail and
reopening the model shows "Unable to resolve interface" errors.

Pattern to always follow:
```matlab
% 1. Add all value types and interfaces
thermalIface = addInterface(dict, "ThermalFluid");
addElement(thermalIface, "Temperature", Type="Temperature");

% 2. Save
dict.save();

% 3. Re-fetch — now safe to pass to setInterface
thermalIface = dict.getInterface("ThermalFluid");
```

---

## Auto-layout

Always call `Simulink.BlockDiagram.arrangeSystem` before `save(model)` — programmatically
added components all start at position (0,0) and stack on top of each other without it.

**For hierarchical models, arrange every decomposed sub-architecture as well as the top level.**
Use the Simulink subsystem path `modelName + "/ComponentName"` for each level:

```matlab
%% Layout and Save
Simulink.BlockDiagram.arrangeSystem(modelName + "/Powertrain");   % sub-levels first
Simulink.BlockDiagram.arrangeSystem(modelName + "/Drivetrain");
Simulink.BlockDiagram.arrangeSystem(modelName);                   % top level last
save(model);
```

Arrange sub-levels before the top level so the top-level layout has accurate size information
for each component block. A sub-architecture that was never arranged remains a collapsed pile,
and the top-level arrange won't fix it.

---

## Re-run Safety for Profile Scripts

Calling `applyProfile` on a model that already has the profile throws a "uniqueness constraint"
error. The cleanest solution is to rebuild the model from scratch at the top of the profile
script — this guarantees a clean slate and makes both scripts independently idempotent.
`buildMySystemProfile` already does this: its first call is always `buildMySystemModel(...)`.

---

## Multi-Domain Interface Patterns

Use `Type="double"` for all elements and document physical units in comments.
Do not use `addValueType` for physical quantities — it creates `Simulink.ValueType`
objects the bus compiler cannot resolve (breaks "update diagram").

See [`code/addCommonInterfaces.m`](code/addCommonInterfaces.m) for an illustrative starting
point covering Thermal, Electrical, Mechanical, and UserCommand interfaces:

```
ifaces = addCommonInterfaces(dict)
```

Returns a struct (`ifaces.ThermalFluid`, `ifaces.ElectricalPower`, etc.). Remember to call
`dict.save()` and re-fetch interfaces before passing them to `setInterface()` — see the
re-fetch pattern above.

---

## Architecture Views — filtered lenses on a large model

Once a model grows beyond a couple of dozen components, navigating it becomes a drag. System Composer's **view architectures** are named, saved lenses that filter the architecture by a stereotype-property query. A view appears in the model canvas dropdown and in the Views Gallery (`openViews(model)`), and matching components glow in the color you gave the view. This is how Gulfstream's eSAM method routinely surfaces cost drivers, high-power components, per-supplier subsets, etc. without hand-drawn diagrams that rot.

Two mechanisms:

**Query-driven views** — a single stereotype-property constraint picks members automatically:

```matlab
import systemcomposer.query.*;
q = PropertyValue("MyProfile.ComponentProperties.Cost_credits") > 150000;
v = createView(model, "CostDrivers", Select=q, Color="#D62728");
```

**Color gotcha:** `Color` accepts hex strings universally (`"#D62728"`) and some but not all named colors — `"red"` and `"blue"` work, `"magenta"` errors with *"The value of 'Color' is invalid. The color must be a hex color or RGB value."* Prefer hex.

`PropertyValue(path)` returns an object that overloads `>`, `<`, `>=`, `<=`, `==`, `~=` to build a query constraint — so the `pv > 150000` expression builds a `systemcomposer.query.Compare` object. Pass it as `Select=`. The view refreshes automatically as stereotype properties change.

To check ad-hoc what a query matches without committing to a view:

```matlab
matches = find(model, q);   % returns a cell array of qualified-name STRINGS,
                            % not Component objects (easy mistake)
for i = 1:numel(matches), disp(string(matches{i})); end
```

**Explicit-element views** — for anything a single property query can't express (allocation-driven groupings, hand-picked subsets, per-supplier partitions), create an empty view and add elements by hand:

```matlab
v = createView(model, "ControlRealization", Color="blue");
v.Root.addElement(arch.getComponent('ControlCabinet'));
for sub = arch.getComponent('ControlCabinet').Architecture.Components
    v.Root.addElement(sub);
end
```

Both mechanisms are additive — you can start with a query and then `addElement` to include extras that didn't match.

### Idempotency when rebuilding

Views are saved *inside* the `.slx`. Our build scripts recreate the `.slx` from scratch on every run, which wipes views along with everything else. So a view-creation script has to run **after** the relevant `buildXxx.m` and be idempotent itself:

```matlab
try, deleteView(model, "CostDrivers"); end %#ok<TRYNC>   % guard against first-run-missing
v = createView(model, "CostDrivers", Select=q, Color="red");
```

See [`code/buildMyViews.m`](code/buildMyViews.m) for a parameterised helper that takes a list of view specs and creates them all.

### `find()` returns strings, not components

A recurring mistake: `find(model, constraint)` returns a `cell` of qualified-name *strings* like `"MyModel/Parent/Sub"`, not `systemcomposer.arch.Component` objects. If you need the component object to, e.g., call `getPropertyValue`, use `model.lookup("Path", pathString)` to resolve it.

---

## Verifying Your Model

Run these checks after every build. They catch real problems that the build step itself
won't flag.

### 1. Check for unconnected ports

Ports that were added but never wired are silently valid at build time but represent
incomplete or inconsistent architecture. See [`code/checkUnconnectedPorts.m`](code/checkUnconnectedPorts.m):

```
checkUnconnectedPorts(modelName)
```

### 2. Update diagram

`set_param` update catches any remaining type resolution issues (e.g. a bad interface
element type that slipped through):

```matlab
set_param("MySystem", "SimulationCommand", "update");
```

**Expected warning for pure architecture models:** `Architecture model contains no
components or all components are virtual.` This is normal — architecture components
have no simulation behaviour. It does not indicate a problem with your model.

If you see a type resolution error instead (e.g. `DataType 'X' did not resolve`), the
cause is almost always an `addElement` call using a value type name instead of a
Simulink base type — see the gotchas table above.

### 3. Check stereotype properties (if using profiles)

```matlab
comp  = arch.getComponent("ComponentA");
props = comp.getStereotypeProperties();
for i = 1:numel(props)
    fprintf("  %s = %s\n", props(i), comp.getPropertyValue(props(i)));
end
```

More from matlab/agent-skills-playground

SkillDescription
agent-skill-authorUse this skill when the user wants to author, design, scope, or refine an Agent Skill (a SKILL.md file). Trigger phrases include "build a new skill", "design an agent skill", "scope a SKILL.md", "how should I structure this skill", "write a skill for X", "my skill isn't working well", or any request to improve an existing SKILL.md. Walks the user through an empirical, test-first process — probe the agent for real failures, design only for genuine knowledge gaps, iterate against runnable examples, and verify across models.
embedded-ai-deployment>
matlab-performance-optimizerOptimize MATLAB code for better performance through vectorization, memory management, and profiling. Use when user requests optimization, mentions slow code, performance issues, speed improvements, or asks to make code faster or more efficient.
matlab-projectUse this skill for any work involving a MATLAB Project (.prj file) — creating a new project, tracking files, managing the project path, configuring Simulink cache and code-generation folders, running project health checks, or writing build scripts that keep the project in sync with the file system. Trigger phrases include "set up a MATLAB project", "create a .prj", "track this file in the project", "project health check", "build script conventions". This skill is the generic foundation; domain-specific skills (e.g. `mbse-workflow`) build on it.
matlab-symbolic-mathGenerate correct MATLAB code using the Symbolic Math Toolbox. Use when the user asks for symbolic computations, analytical solutions, symbolic differentiation/integration, equation solving, or converting symbolic results to numeric MATLAB functions. Also use when converting differential equations to transfer functions or state-space form.
matlab-uihtml-app-builderBuild interactive web applications using HTML/JavaScript interfaces with MATLAB computational backends via the uihtml component. Use when creating HTML-based MATLAB apps, JavaScript MATLAB interfaces, web UIs with MATLAB, interactive MATLAB GUIs, or when user mentions uihtml, HTML, JavaScript, web apps, or web interfaces.
matlab-uihtml-designGenerate beautiful, distinctive HTML/CSS/JS control panels for MATLAB uihtml components. 8 built-in styles (Clean, Material, Cosmic Dark, Neumorphic, Dashboard Light, Midnight Gradient, Minimal Mono, Warm Dark) plus custom aesthetics. Produces production-grade UI with sliders, buttons, toggles, and panels. Use when building visually polished MATLAB app UIs with uihtml.
mbse-architectureUse this skill for the architecture phases of an MBSE workflow in MATLAB, when writing idempotent buildXxx.m scripts that produce a three-layer RFLPV architecture (Functional, Logical, Physical) with interface dictionaries, stereotype profiles, allocation sets, and requirements Implement links. Trigger for defining stereotype properties, functional-to-logical / logical-to-physical allocation, mapping requirements to components via slreq Implement links, or running quantitative roll-up analysis on the architecture. Do NOT trigger for ad-hoc structural edits to an already-built System Composer model (adding one component, rewiring a port) — use `building-simulink-models` with `model_edit` for that. Works alongside the `system-composer` skill for detailed SC API patterns.
mbse-workflowUse this skill for guided MBSE work in MATLAB — starting a new project, resuming work mid-workflow on an existing project, or answering orientation questions about how the MBSE skills fit together. Trigger when the user says they want to create, start, or set up a new MBSE project; work on a model-based systems engineering / RFLPV project; or asks which skill covers which phase. Walks through phases one at a time — propose → approve → generate → run → confirm. Use proactively whenever someone mentions starting or continuing an MBSE project.
simulink-requirementsUse this skill for all requirements-related work in a MATLAB MBSE project using the Requirements Toolbox (slreq). Covers creating and populating requirement sets, derivation links, test case requirements, verification coverage, reading and tracing links across requirement sets and models, checking link health, allocating requirements to components (Implement links), and building traceability reports. Trigger when the user asks about slreq API, slreqx files, slmx link files, outLinks/inLinks, traceability matrices, coverage analysis, broken links, or mapping requirements to architecture components. Use proactively for any requirements or traceability task.