parameter-design
Skill213 starsChanged 2 months agoMITView on GitHub
MUST READ before creating or designing custom parameters on any COMP: pages, styles, ranges, help text, naming, ParGroup gotchas.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- selamy-labs/agent-skillstheir own version, first seen Nov 8, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- selamy-labs/agent-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository30 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- web/llms-full.txtllms.txt
- web/llms.txtllms.txt
- add-mcp-toolSkill
- agent-testsSkill
- briefSkill
- build-uiSkill
- collabSkill
- create-extensionSkill
- create-operatorSkill
- debug-operatorSkill
- externalize-operatorSkill
- glsl-shadersSkill
- manage-annotationsSkill
- mcp-tools-referenceSkill
- merge-divergent-toxSkill
- movie-exportSkill
- multi-instanceSkill
- multi-session-etiquetteSkill
- operator-gotchasSkill
- pop-networksSkill
- publish-specimenSkill
- releaseSkill
- run-testsSkill
- specimen-authoringSkill
- td-api-referenceSkill
- td-recoverySkill
- testingSkill
- visual-aestheticsSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 50
Apply declared attributes symmetrically: If you set min when creating, set it when the par already exists too, or a schema change never …
- Distinctive: line 79
Never swallow the exception silently: a handler that raises into nothing is indistinguishable from a parameter that does nothing.
- Very specific: line 96
Every custom parameter MUST have help text set via par.help = "...".
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- Generated by Embody/Envoy - Do not remove this comment - sha:5454b92f34285f1a -->Parameter Design
Ownership and Lifecycle
Code owns the schema; the user owns the value. A parameter's name, style, page, range, default and help text are yours to declare and re-assert. Its VALUE is the user's, and nothing you write may quietly discard it.
Extensions reinitialize on every source-file save, so every creation path must be get-or-create, never create-blindly:
def ensureCustomPage(comp, name):
for page in comp.customPages:
if page.name == name:
return page
return comp.appendCustomPage(name)
def ensureCustomPar(comp, page, name, style, **attrs):
"""Get-or-create one custom par: the Par, or the ParGroup for a tuplet."""
# Probe the WHOLE COMP by tupletName. Names are a flat per-COMP namespace,
# and a multi-value style (RGB, XYZ) is stored as <name>r/<name>g/<name>b,
# so a probe by .name never finds it and append*() (replace=True) would
# re-create it on every reinit.
found = [p for p in comp.customPars if p.tupletName == name]
if not found:
getattr(page, 'append' + style)(name)
found = [p for p in comp.customPars if p.tupletName == name]
elif found[0].style not in STYLE_FAMILY.get(style, (style,)):
raise ValueError('par %s is %s, declared %s -- refusing to replace it'
% (name, found[0].style, style))
for p in found: # symmetric: schema attrs re-applied
for key, value in attrs.items(): # on every reinit; the user's state
if key not in ('val', 'expr', 'bindExpr', 'mode', 'export'):
setattr(p, key, value) # is never touched -- and a typo RAISES
return found[0] if len(found) == 1 else found[0].parGroup
# TD reports a tuplet's style by family: RGB reads 'RGBA', XY/XYZ read 'XYZW'.
STYLE_FAMILY = {'RGB': ('RGB', 'RGBA'), 'XY': ('XY', 'XYZW'), 'XYZ': ('XYZ', 'XYZW')}Four rules that make it safe:
- Probe the whole COMP, not the page. Custom parameter names are a flat per-COMP namespace, and
append*()defaults toreplace=True: a page-scoped probe will happily destroy a same-named par living on another page. - Re-read after appending, by
tupletName.append*()returns a ParGroup, and a multi-value style is stored as component pars (Tintr/Tintg/Tintb) whosetupletNameis the name you declared;comp.par['Tint']isNone. Embody ships this exact helper asop.Embody.op('embody_pardef').module.ensureCustomPar. - Never
destroy()on style drift.Par.destroy()takes the user's value, expressions and exports with it. Report the mismatch and refuse; removal is a separate, deliberate act. - Apply declared attributes symmetrically. If you set
minwhen creating, set it when the par already exists too, or a schema change never reaches existing installs.
Parameter Callbacks
One dispatcher, not one promoted method per parameter. A parexec DAT that grows an elif par.name == ... chain drags a public-tier method onto the COMP for every branch (the three tiers in td-python.md). Name the handler for its parameter and let the DAT find it:
# the WHOLE onValueChange in the parexec DAT
def onValueChange(par, prev):
ext = parent.MyComp.ext.MyExt
handler = getattr(ext, '_on%sValueChange' % par.name, None)
if handler is None:
return
try:
handler(par, prev)
except Exception as e:
parent.MyComp.Error('%s handler failed: %s' % (par.name, e))# on the extension: tier 3, invisible on the COMP
def _onSpeedValueChange(self, par, prev):
self.ownerComp.op('./timer1').par.speed = par.eval()
def _onResetPulse(self, par):
self.rebuild()- Audit once at init that every
_on*handler matches a real parameter: a renamed par turns its handler into dead code that fails no test; the audit turns that into a warning. - Never swallow the exception silently: a handler that raises into nothing is indistinguishable from a parameter that does nothing.
- Pulse handlers take
(par); value handlers take(par, prev). The Parameter Execute DAT ships withbuiltinON as well ascustom: turnbuiltinoff for a custom-parameter dispatcher (/operator-gotchas).
Parameter, Storage, or Dependency?
Custom parameters are one of three ways to hold state on a COMP, and the choice is about lifetime, not taste:
| The state is | Use | Lives on | Survives an extension reinit | Survives a tox reload / TDXN rebuild |
| User-facing, persisted, part of the COMP's interface | custom parameter | the COMP | yes | yes |
| Durable bookkeeping the user should not see | storage (write through store()) | the COMP | yes | no (wiped) |
| A derived value that must recook whatever reads it | tdu.Dependency | the extension instance | no (rebuild in __init__) | no |
Publish once and let readers pull; never push a value into each consumer. comp.storage[k] = v, in-place mutation of a stored container, and a plain self.attr all update the value and notify nobody; store() and dep.val = x notify. fetch() without a default RAISES, fetch() searches UP the parents, unstore() is glob-matched, and a bind expression to a Dependency is two-way. The measured tables, the storage pickling rules and the Dependency traps are in references/state-lifetimes.md in this skill's folder (get_guidance(topic='parameter-design/state-lifetimes')).
Help Text
Every custom parameter MUST have help text set via par.help = "...". It appears as the tooltip when users hover the parameter name. Describe what the parameter controls and what its values mean.
- Good:
"Maximum number of rows displayed in the manager list. Set to 0 for unlimited." - Bad:
"Max rows"(restates the label) - Unacceptable: no help text at all
In TDXN files, "help": "..." rides in the parameter definition; Embody exports and imports it.
Section Breaks, Ordering, Pages
par.startSection = Trueon the first parameter of each logical group draws a separator above it ("startSection": truein TDXN).- Parameters appear in the order appended: primary controls first, secondary and advanced after, read-only status last. Reorder with
par.order(floats such as11.5insert between neighbours). - Group into pages by function with
comp.appendCustomPage('PageName')(creation order). Common pattern: Main or Settings, Tags, UI, About (read-only version, build, author).
Naming and Styles
Names start with one uppercase letter, then lowercase letters and digits only (Speed, Maxrows, Autosave, Envoyenable); no underscores, spaces or special characters.
| Use case | Style | Notes |
| On/off toggle | Toggle | Values are 0/1 |
| Fire-once action | Pulse | No persistent value |
| Enumerated choices | Menu | Set menuNames and menuLabels separately |
| Editable dropdown | StrMenu | Free text with suggestions |
| Numeric value | Float or Int | Set the range properties below |
| Text input | Str | Free-form string |
| File or folder path | File / Folder | Opens a system dialog |
| Operator reference | OP, COMP, TOP, CHOP, SOP, DAT, MAT | Filtered by family |
| Section header | Header | Visual label only, no value |
Numeric Ranges, Read-Only, Defaults
min/max are the limits, clampMin/clampMax decide whether they are enforced, normMin/normMax are what the slider covers visually:
p = page.appendFloat('Speed', label='Speed')[0]
p.default = 1.0
p.min = 0.0; p.max = 10.0
p.clampMin = True; p.clampMax = False # values above 10 allowed by typing
p.normMin = 0.0; p.normMax = 5.0 # slider covers 0-5
p.help = "Playback speed multiplier. 1.0 = normal speed."par.readOnly = Truefor status and informational parameters (version, build, connection state).- Always set
par.default: it enables Revert to Default and keeps TDXN round-trips consistent..defaultnever sets the value: afterappendFloat+p.default = 5.0the par still evaluates 0.0, and a latermin/clampMinclamps it to the min, not the default (verified 2025.33230, issue #94). Setp.val = p.defaulton the CREATE branch of get-or-create only, so a user's value is never overwritten. - All
page.append*()methods return a ParGroup: index with[0]for the Par.
Sequence Parameters
Resizable blocks (glslTOP uniforms, constantCHOP channels, mathmixPOP combines, custom sequences). Function Store's note on issue #94, "building sequence pars takes a bit more thinking than it should", is fair: three things are easy to get wrong.
- Reach the sequence, not the parameter.
comp.seq.<name>from the operator, orpar.sequencefrom any block parameter. The individual parameters are named<seq><index><par>(uniname0,uniname1); never build those names by hand when the sequence object is there. numBlocksis the get-or-create. Assigning it grows or shrinks in one step and is idempotent, which is what an extension that reinitializes on every save needs.insertBlock(i)/destroyBlock(i)only when position matters.
seq = comp.seq.uni
seq.numBlocks = max(seq.numBlocks, len(uniforms)) # grow, never shrink blindly
for i, u in enumerate(uniforms):
seq[i].par.uniname = u.name # block i, by index- Shrinking destroys values.
destroyBlockor loweringnumBlockstakes the block's values, expressions and exports with it, exactly likePar.destroy(). Code owns the block COUNT only where the count is genuinely derived; if the user can add blocks, grow to fit and leave the excess alone. - In Embody's TDXN, sequences are stored by BASE name (
sequences:keyed by sequence name, blocks holding only non-default values underuniname, notuniname0); the default block count is probed from a throwaway instance, and blocks are created in import Phase 2.5 before Phase 3 sets parameters. Full detail:docs/tdxn/specification.md.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

