Advanced
•6 min read
Definition Meta
Add optional metadata to any definition for better debugging, richer devtools, and self-documenting systems.
Meta is supported on all 7 definition types: modules, facts, events, constraints, resolvers, effects, and derivations.
Why Meta?
Directive's inspect(), explain(), and devtools show definition IDs like auth::needsLogin or fetchUser. Meta lets you attach human-readable context – labels, descriptions, categories, and colors – so these surfaces are immediately useful without checking source code.
Meta is purely informational. It is never read during reconciliation – zero runtime overhead.
Adding Meta
Every definition type (constraints, resolvers, effects, derivations) accepts an optional meta field.
Modules
const authModule = createModule("auth", {
schema: { /* ... */ },
meta: {
label: "Authentication",
description: "Handles login, tokens, and session management",
category: "auth",
},
// ... constraints, resolvers, etc.
});
Module meta surfaces in inspect().modules and system.meta.module(id). Useful for architecture diagrams, module health dashboards, and onboarding documentation.
Facts (Schema Fields)
schema: {
facts: {
score: t.number().meta({ label: "Player Score", category: "data" }),
email: t.string().meta({ label: "Email", tags: ["pii"] }).nullable(),
plain: t.number(), // no meta — works as before
},
}
The .meta() method chains with all schema builder methods (.nullable(), .min(), .maxLength(), etc.). Surfaces in inspect().facts and system.meta.fact(key). Use tags: ["pii"] to mark fields for compliance scanning.
Events
events: {
// Function form (no meta) — unchanged
increment: (facts) => { facts.count += 1; },
// Object form with meta
reset: {
handler: (facts) => { facts.count = 0; },
meta: { label: "Reset Counter", category: "ui" },
},
},
Event meta surfaces in inspect().events and system.meta.event(name).
Constraints
constraints: {
needsLogin: {
when: (facts) => !facts.user,
require: { type: "LOGIN" },
meta: {
label: "Requires Authentication",
description: "User is not logged in",
category: "auth",
color: "#f59e0b",
},
},
},
Resolvers
resolvers: {
login: {
requirement: "LOGIN",
resolve: async (req, context) => { /* ... */ },
meta: {
label: "OAuth Login Flow",
description: "Exchanges authorization code for access token",
},
},
},
Effects
effects: {
logPhase: {
run: (facts) => console.log(facts.phase),
meta: { label: "Phase Logger", category: "logging" },
},
},
Derivations
Derivations support two forms. The existing function form works exactly as before. The new object form uses compute instead of a bare function:
derive: {
// Function form (no meta) – unchanged
isReady: (facts) => facts.status === "ready",
// Object form with meta
displayName: {
compute: (facts) => `${facts.firstName} ${facts.lastName}`,
meta: { label: "Display Name", description: "Full name for UI" },
},
},
The DefinitionMeta Type
import type { DefinitionMeta } from "@directive-run/core";
interface DefinitionMeta {
label?: string; // Human-readable name
description?: string; // Longer explanation
category?: string; // Grouping key (e.g., "auth", "data", "ui")
color?: string; // CSS hex color (e.g., "#f59e0b")
tags?: string[]; // Multi-dimensional labels for filtering
[key: string]: unknown; // Extensible for plugins
}
You don't need to import DefinitionMeta to use it – inline objects are inferred correctly. The import is useful when building reusable helpers or plugins.
Suggested Categories
| Category | Use for |
|---|---|
auth | Authentication and authorization |
data | Data fetching and persistence |
ui | UI state and display logic |
logging | Logging and analytics |
lifecycle | System lifecycle management |
Categories are freeform strings – custom categories work, these are conventions for consistency.
Tags
Use tags for multi-dimensional filtering. A constraint can be both "auth" and "critical":
constraints: {
needsLogin: {
when: (facts) => !facts.user,
require: { type: "LOGIN" },
meta: {
label: "Requires Auth",
category: "auth",
tags: ["critical", "security"],
},
},
},
Tags are separate from category – use category for primary grouping, tags for cross-cutting concerns.
Where Meta Surfaces
system.meta (O(1) queries)
Query meta directly by definition ID without building a full inspection snapshot:
// O(1) lookups
system.meta.module("auth")?.label; // "Authentication"
system.meta.constraint("needsLogin")?.label; // "Requires Auth"
system.meta.resolver("login")?.description; // "Exchanges code for token"
system.meta.effect("log")?.category; // "logging"
system.meta.derivation("displayName")?.tags; // ["ui", "critical"]
// Returns undefined for unknown IDs
system.meta.constraint("nonexistent"); // undefined
// Bulk queries across all definition types
system.meta.byCategory("auth"); // MetaMatch[] — all auth definitions
system.meta.byTag("pii"); // MetaMatch[] — all PII-tagged fields
system.meta.byTag("critical"); // MetaMatch[] — all critical definitions
Each MetaMatch contains { type, id, meta } where type is "module" | "fact" | "event" | "constraint" | "resolver" | "effect" | "derivation".
Use byTag("pii") for compliance scanning, byCategory("auth") for architecture dashboards.
Use system.meta in plugins, custom devtools, and AI tooling for efficient meta access.
inspect()
system.inspect() includes meta on all seven definition types:
const inspection = system.inspect();
for (const c of inspection.constraints) {
console.log(c.id, c.meta?.label ?? c.id);
}
for (const r of inspection.resolverDefs) {
console.log(r.id, r.meta?.label ?? r.id);
}
for (const e of inspection.effects) {
console.log(e.id, e.meta?.label ?? e.id);
}
for (const d of inspection.derivations) {
console.log(d.id, d.meta?.label ?? d.id);
}
// Modules too
for (const m of inspection.modules) {
console.log(m.id, m.meta?.label ?? m.id);
}
explain()
system.explain() uses meta.label instead of the raw constraint ID and includes meta.description when present:
Without meta:
Requirement "LOGIN" (id: req-1)
├─ Produced by constraint: needsLogin
├─ Constraint priority: 0
With meta:
Requirement "LOGIN" (id: req-1)
├─ Produced by constraint: Requires Authentication
├─ Constraint priority: 0
├─ Description: User is not logged in
Devtools
The devtools plugin reads meta from inspect() and uses it for:
- Node labels in the constraint/resolver graph
- Color coding by category
- Tooltips with descriptions
Trace Enrichment
When trace: true is enabled, every trace entry automatically carries meta inline on its sub-arrays:
const sys = createSystem({ module: mod, trace: true });
sys.start();
// After reconciliation...
const entry = sys.trace![0];
entry.constraintsHit[0].meta?.label; // "Requires Auth"
entry.resolversStarted[0].meta?.label; // "OAuth Login Flow"
entry.factChanges[0].meta?.label; // "Player Score"
entry.effectsRun[0].meta?.label; // "Phase Logger"
entry.derivationsRecomputed[0].meta?.label; // "Display Name"
Meta flows with the causal event – trace viewers show human-readable labels without secondary lookups.
AI Agent Context
The @directive-run/ai package can inject system meta into LLM prompts automatically:
import { createAgentOrchestrator } from "@directive-run/ai";
const orchestrator = createAgentOrchestrator({
runner,
metaContext: true, // Inject system meta into agent instructions
});
Or build context manually with toAIContext() and formatSystemMeta():
import { toAIContext } from "@directive-run/ai";
const context = toAIContext(system);
// Returns formatted markdown with modules, constraints, resolvers,
// facts, events, effects, derivations — only annotated definitions
The output is token-efficient – sections with no annotated definitions are omitted entirely.
Dynamic Definitions
Meta works with dynamically registered definitions:
system.constraints.register("dynamicCheck", {
when: (facts) => facts.score < 0,
require: { type: "FIX_SCORE" },
meta: { label: "Score Validator", category: "data" },
});
Security
Meta objects are frozen at registration time using Object.create(null) + Object.freeze. This prevents:
- Prototype pollution (no
__proto__chain) - Mutation after registration
- Accidental shared state between definitions
Production Bundles
Meta values are string literals that survive minification and ship in production bundles. This is by design – meta powers production features like AI agent reasoning, compliance scanning (tags: ["pii"]), and observability dashboards.
Avoid putting sensitive information in meta fields:
- Internal API paths (e.g.,
/api/internal/auth/refresh) - Employee names or personal information
- Sensitive business rules you don't want visible in client bundles
Use generic labels instead: "Auth Check" rather than "Refreshes JWT via /api/internal/auth/refresh".
Next Steps
- Constraints – Full constraint API
- Resolvers – Full resolver API
- Effects – Full effects API
- Derivations – Full derivations API
- DevTools – Visualize meta in the devtools panel

