semantius-agent-maker
A skill for authoring ONE model-aware Agent Skill against a Semantius semantic model, where the user supplies the task. The output is a SKILL.md package the user can install and a calling agent can invoke.
This skill forks the authoring framework from Anthropic's skill-creator
(adapted for Semantius model awareness) and composes with use-semantius at
runtime (the maker references it; generated skills declare it as a co-loaded
dependency for CLI mechanics).
When to use this skill vs. its siblings
| Situation | Use |
|---|---|
| User says "generate skills for this model" (no specific task) | semantius-skill-maker |
| User says "build a skill that does X with this model" (task supplied) | this skill |
| User wants to bundle multiple existing skills into an A2A agent card | (separate concern; not this skill's job) |
| User wants the live module reverse-engineered into a model file | semantic-model-optimizer |
The defining property of this skill: the user brings the goal. The maker's job is to interview them, find the slice of the model the goal touches, and write a tight, model-aware SKILL.md around it.
Schema compatibility: EXPECTED_MAJOR = 3
This skill expects model files written by semantic-model-analyst major 3. Check the model file's frontmatter version: "MAJOR.MINOR" at the start of Step 1. Major must equal EXPECTED_MAJOR, minor is informational. Mismatch handling mirrors semantius-skill-maker:
- Older major — route the user back to the analyst's Mode D (Rebuild) to migrate the file, then re-run agent-maker.
- Newer major — tell the user to update
semantius-agent-makerbefore retrying. - Missing
versionkey — treat as major0; same response as older-major.
When the analyst's major bumps, this skill's EXPECTED_MAJOR must move in lock-step with semantic-model-analyst, semantic-model-deployer, and semantius-skill-maker. The quartet shares the major.
Inputs (ask for these up front if not provided)
- Path to a
*-semantic-model.mdin the working directory. Required. If missing, the model can be reverse-engineered withsemantic-model-optimizer, but do not invent one. - The task in plain English. A one- or two-sentence description of what
the skill should do. Examples:
- "Research point solutions in a given domain and add the good ones to the catalog."
- "Given a candidate, run a background check workflow and record the result."
- "Compare ingredient alternatives by price across vendors and pick one."
- Target output path. Propose
<cwd>/.claude/skills/<generated-skill-name>/SKILL.mdand let the user override. The generated skill is one folder; bundled resources are optional.
Do not start writing until you have all three. If the user is vague on the task, interview them (next section) before drafting anything.
Workflow
Step 1 — Read the model and use-semantius references
Gate on the model file's frontmatter version major first (see "Schema compatibility" above). Refuse and route the user if the major doesn't match EXPECTED_MAJOR = 3; do not parse anything else when the gate fails.
Once the version gate passes, read the supplied model file end to end. Build a mental index of:
- Entity table names and singular labels
- Fields per entity (especially required fields, enums, FK shapes)
- Module slug (URLs use
module_slug, lowercase) - Module type — read frontmatter
module_type("domain"or"master", default"domain"when absent). Amastermodule hosts shared / master data consumed by multiple domain modules; writes to its entities are visible to every consuming domain via cross-modulepermission_hierarchyrows. When the task's slice (Step 3) touches a master module, the generated skill must surface a cross-domain-impact callout on every write recipe. - Per-entity
**Shared master cluster:** <cluster>annotation — note which entities in this (domain) module are flagged as classic master concepts. This is an authoring hint: those entities may have been (or may soon be) promoted to a master module on re-deploy. Treat the annotation as informational; if the user's task writes to such an entity, include a one-line "shared concept across domains" note in the generated skill so the calling agent is aware. - Label composition rules and lookup conventions
- Cross-cutting data rules and guardrails
- Entity-level
Select rulesub-blocks — note which entities carry a non-emptyselect_rule. Capture the rule's plain-English uniform per-row predicate (from the sub-block'sdescription, the entity's §3 prose, or a paraphrase of the JsonLogic intent). The rule applies uniformly to every caller withview_permission— the platform evaluates the JsonLogic body per row with$today/$now/$user_idas reserved variables; there is no documented mechanism by which holding a specific permission causes the rule to be skipped. If the model's §7 carries an explicit architectural-decision entry naming a documented broadening mechanism (a separate cube view / entity surface admins read, or a PostgresBYPASSRLSrole attribute), capture that mechanism alongside the predicate. Never read the §2 Permissions summary for a<slug>:view_all_<plural>-style permission and assume the platform honors it as a bypass — that mechanism is not in the current platform spec; promising it in the generated skill ships broken RBAC. - Field-level
Input type rulesentries — note which fields carry a non-emptyinput_type_ruleand what trigger the rule'sdescription(or JsonLogic intent) names. Two patterns matter at authoring time: (a) rules that flip a field's effective mode torequiredon a sibling-field transition (typically*_athousekeeping fields likeapproved_at,closed_at,submitted_at) — when the user's task drives that transition, the recipe must include the dependent field in the same PATCH body; (b) rules that flip a field toreadonlyafter a terminal state — recipes that update the entity after that state should drop the locked field from their PATCH body.
Be aware of use-semantius's references/ directory (cli-usage,
data-modeling, rbac, crud-tools, cube-queries, cube-tools, webhook-import).
You do not need to inline these — the generated skill will declare
use-semantius as a dependency and the runtime will load them as needed.
Step 2 — Interview the user
Forked from skill-creator's "Capture Intent" + "Interview and Research".
Adapt for Semantius. Get answers to:
What should the skill enable an agent to do? Concrete goal, in the user's own words.
When should the skill trigger? Phrasings a real user would type.
What entities does the goal touch? Validate against the model. If the user names an entity that doesn't exist, ask whether to add it (via
semantic-model-deployerlater) or pick the nearest existing one.Where does non-deterministic input come from? Web search? An external API? The user's own judgment in conversation? None (pure CRUD)?
Where does the skill end? Read-only report? Write to the catalog? Multi-step write with user approval gates?
Approval gates. For writes, must the agent ask before each write, or batch and ask once at the end? Default: ask once per logical unit of work (one entity creation = one approval, a batch import = one approval).
Idempotency. Re-running the skill on the same inputs — should it no-op, append, or update? The default for "research X and write to catalog" is read-first-then-create-if-missing, never blind insert.
Failure handling. What's a domain error (refuse) vs. a transient error (retry once)?
Read scope (when any entity the task reads has a
select_rule). Ask: "The model applies a uniform per-row filter on<entity>— every caller withview_permission, including the one running this agent, sees only<plain-English predicate from the rule>. Does this skill respect that uniform scope (the agent acts on what the caller can see), or does the task genuinely need to read rows outside the predicate?"The default is respect the scope; this is the simplest answer and is correct for most agent skills. If the user needs broader read access, the path depends on what the model file's §7 already resolves:
- If §7 carries an architectural-decision entry naming a documented
broadening mechanism (option C separate cube view / entity surface,
option D Postgres
BYPASSRLSrole provisioned outside Semantius), wire the generated skill's read step to that mechanism explicitly. - If §7 does NOT resolve a broadening mechanism, the skill cannot
promise broader read access. The generated skill says so plainly:
"broader read access requires running as a caller who holds the
role with broader visibility; escalate or hand off". Do NOT propose
a
<slug>:view_all_<plural>permission as a bypass; that mechanism is not documented in the current platform spec, and a generated skill that names it ships broken RBAC. If the user thinks they need broader access and §7 doesn't cover it, the correct route is to send the user back tosemantic-model-analystto resolve Stage 12's architectural-decision question before generating the skill.
- If §7 carries an architectural-decision entry naming a documented
broadening mechanism (option C separate cube view / entity surface,
option D Postgres
Proactively suggest answers based on what the model implies. Don't make the user fill every blank from scratch.
Step 3 — Identify the model slice
Do NOT inline the whole model. Identify only the entities the skill reads or writes, plus their FK closure (entities referenced by required FKs of the touched entities).
For each entity in the slice, the generated skill will need:
- Table name and singular label
- Required fields with type and any enum values
- FK fields with the target entity and its key column
- Label composition rule (so the generated skill can build display labels)
- Any cross-cutting rule that applies (e.g. "soft-deactivate, never hard-delete")
- Master-module / shared-concept callout — when the entity sits in a
module_type: mastermodule, OR carries a**Shared master cluster:** <cluster>annotation in §3, record this on the slice. The generated SKILL.md surfaces a one-line "Writes to this entity are visible across consuming modules" guardrail on every write recipe touching it; the calling agent should treat the data as shared, not module-private. The slice note does not change the recipe shape (the CLI call is identical), only the surrounding prose. - Row-level read scope when the entity carries a non-empty
select_rule. Pull the uniform per-row predicate from the Step 1 mental index. The slice notes this so the generated SKILL.md can surface the visibility callout. If the model's §7 named a documented broadening mechanism, capture it; if §7 did NOT, the slice notes only the uniform predicate and routes broader-access requests to escalation — never invents aview_all_<plural>permission bypass. - Conditional UI states for any field on the entity that carries a non-empty
input_type_ruleAND whose trigger or target lies on the user's task path. Cross-check the user's task against the rule's trigger condition: when the task drives the transition the rule keys on (e.g. the task is "approve the offer" andapproved_athasinput_type_rule: hidden until status=approved), record(entity, field, trigger_field, trigger_value)as a paired side-effect — the recipe must set the dependent field in the same PATCH as the trigger. Fields whose rule is unrelated to the task path are noise; keep the slice tight.
Entities not in the slice should be referenced by name only (one-line note in the generated skill: "this domain also has X, Y, Z — out of scope").
Step 4 — Decide the skill's shape
Three shapes, pick one (or hybrid):
- Pure recipe — deterministic, fixed steps, no reasoning. Closer to what
semantius-skill-makerproduces. If the user's task is really this, push back and suggest they runsemantius-skill-makerinstead. - Pure reasoning — judgment, web research, summarization. No model writes. Rare — usually the user also wants the result captured somewhere.
- Hybrid (most common) — reason at the top (research, compare, decide),
act at the bottom (write findings to the model via
use-semantiusrecipes). The reasoning section is open-ended; the action section is a tight recipe.
Name the shape explicitly in the generated SKILL.md so the calling agent knows what to expect.
Step 5 — Draft the generated SKILL.md
Use the structure in the "Generated skill anatomy" section below. Inline
only the model slice from Step 3. Reference use-semantius as a co-loaded
dependency in the frontmatter description (so the runtime loads it
alongside).
Length target: ≤ 500 lines for the generated SKILL.md. If the task genuinely
needs more, split into reference files under references/ and point at them
from SKILL.md.
Step 6 — Self-review pass
Before showing the draft to the user, check:
- Frontmatter description is "pushy" — lists concrete trigger phrases, explains when to use vs. skip.
- The model slice is inlined; entities outside the slice are named but not detailed.
- Every model write uses the
--singlepattern fromuse-semantiuswhen exactly-one is expected; array pattern when zero-or-many is the normal answer. - Every recipe step has an explicit exit-code contract.
- Read-before-write is enforced anywhere a duplicate is possible.
- Approval gates are placed where the user said they should be.
- The "runtime disagreement" rule is included: if the live schema contradicts the inlined slice, abort and recommend regenerating — do not silently adapt.
- FK shapes match what the model says today (re-read the model section, do not rely on memory).
- (Analyst v2.2+) Every read recipe against an entity in the slice that
carries a
select_ruleincludes a one-sentence visibility callout ("you will see only<plain-English scope>"), either inline in the recipe or in the entity's slice block. A read with no callout against a scoped entity is the silent-empty-result defect — a missing row is indistinguishable from no-such-row, and the agent draws the wrong conclusion. - (Analyst v2.2+) No recipe duplicates a
select_rulein its own GET filter. If the entity's rule filters onsubmitter_user_id = $user_id, the recipe's path does NOT add the same filter — the platform applies it via RLS, and the client-side duplication is dead code that breaks when the analyst updates the rule. - (Analyst v2.2+) Every transition recipe that fires a trigger named in
the slice's "Conditional UI states" block sets the dependent field in
the same PATCH body. Example: when the slice records
approved_at's trigger asstatus = "approved", the "approve offer" recipe's PATCH includes bothstatus = "approved"andapproved_at = $now. A transition that sets the trigger without the dependent is the silent-empty-housekeeping-field defect — the UI would have rendered the dependent asrequiredat form time, but the recipe has no form. - (Analyst v2.2+) No recipe interprets the JsonLogic body of an
input_type_rule. The rule is for the form-rendering layer; recipes derive their semantics from the task's own intent (the user's interview answers, the entity'svalidation_rules), not by re-evaluating the UI rule's body. - (Analyst v2.2+ critical rule) No prose anywhere in the generated
skill promises a
<slug>:view_all_<plural>-style permission bypass ofselect_rule. Walk every preamble line, every JTBD visibility callout, every Approval-gates entry, every Failure-modes block. If any prose names a permission code as a path to "see every row" / "bypass the filter" / "broader read access" / "elevated tier", the ONLY acceptable shape is: (a) the source model's §7 explicitly resolves the broadening mechanism (option C separate cube view, option D PostgresBYPASSRLSrole attribute) AND the prose names THAT mechanism, not a permission code; OR (b) delete the prose entirely and route broader-access requests to "escalate to a caller who holds the role". A prose promise of a permission bypass that the platform does not honor is the canonical v2.2 defect — the generated skill looks right but silently ships broken RBAC. - (Analyst v3.0+) Every write recipe against an entity sitting in a
module_type: mastermodule, or carrying a**Shared master cluster:**annotation in the source model, includes a one-line cross-domain-impact callout ("writes to<entity>are visible to every consuming domain module"). A silent write against a shared entity is a recipe defect: the calling agent may assume the data is module-private and reason incorrectly about what its write touches.
Step 7 — Confirm output path and write
Default: <cwd>/.claude/skills/<generated-skill-name>/SKILL.md. The skill
name should be <model-slug>-<verb-phrase> — e.g.
enterprise-research-domain, workforce-onboard-employee,
ats-screen-candidate. Verb-first reads cleanly as "what the skill does."
Confirm with the user before writing. If they want the skill under
agents/<domain>/<skill-name>/ instead (alongside an eventual agent card),
honor that.
Step 8 — Show the user and iterate
Surface the draft. Ask the user to react to:
- Does the trigger description match how they'd ask for this?
- Is the model slice right, or are there entities you missed/over-included?
- Do the approval gates land where they want them?
- Does the recipe at the bottom look correct for the writes they expect?
Iterate until the user says it's right. Don't add a heavyweight eval loop in v1 — qualitative review is enough.
Generated skill anatomy
The skill this maker produces follows this structure:
<generated-skill-name>/
└── SKILL.md
├── Frontmatter (name, description)
├── # Purpose (1 paragraph)
├── ## Inputs (what the calling agent must supply)
├── ## Model slice (entities, FKs, enums, label rules)
├── ## Workflow (the reasoning + acting steps)
├── ## Recipes (the deterministic writes, with --single / array patterns)
├── ## Approval gates (where to pause and ask)
├── ## Guardrails (what the skill refuses to do)
└── ## When the runtime disagrees with this slice (abort + regenerate)
Bundled scripts/ or references/ are optional and rare for v1 outputs.
Add them only if the SKILL.md crosses 500 lines or if the skill bundles
reusable shell scripts that benefit multiple invocations.
Frontmatter description rule (pushy triggering)
The description is the primary trigger mechanism. Make it:
- Specific to the model and task (mention entities by name)
- List multiple phrasings a real user would type
- Explain when to use AND when to skip
- Slightly "pushy" — Claude tends to undertrigger skills
Bad: "A skill for the enterprise model."
Good: "Research point solutions for a given domain in the enterprise model. Use whenever the user wants to add solutions/vendors to the catalog, asks 'what solutions exist for ITSM/CRM/HRIS/...', wants to compare alternatives, or needs vendor + pricing context for a domain. Pulls web research, dedupes against the existing catalog, and writes new solutions + vendors + their domain mappings via use-semantius after user approval. Skip when the user just wants to read the existing catalog (use a plain query instead)."
Model-slice section
For each entity in the slice, write a block like:
### `<table_name>` (<singular_label>)
**Module:** `<module_slug>`
**Required fields:**
- `<name>` (<type>) — <one-line meaning>
- `<name>` (<type>, enum: `value1` | `value2` | `value3`)
**FKs:**
- `<field>` → `<target_table>.<key>` (format: reference)
**Label rule:** `<how the display label is composed>`
**Lookup convention:** `<how to resolve user input to a row>`
**Row-level read scope** *(omit when the entity has no `select_rule`)*:
- Every caller with `view_permission` sees only rows where `<plain-English
uniform predicate>`. <Optional, only when the source model's §7
explicitly resolves a broadening mechanism: "Broader read access for
`<role>` is provisioned via `<mechanism — separate cube view, Postgres
BYPASSRLS role, etc.>` per the model's §7."> **NEVER write a line like
"callers holding `<slug>:view_all_X` bypass the filter"** — that
mechanism is not in the platform spec; promising it ships broken RBAC.
When the user's task genuinely needs broader access and the model's §7
has not resolved a mechanism, the recipe says "escalate to a user who
holds the role with broader visibility" and does not pretend to grant it.
- The platform applies this filter via RLS on every read; this skill does
not duplicate the rule in its own query filters.
**Conditional UI states** *(omit when no field on this entity has an
`input_type_rule` on the task path)*:
- `<field>`: <trigger gist, e.g. "becomes required when `status` is being
set to `approved`; locks to readonly after save">. The recipe at the
bottom sets this field in the same PATCH as the trigger.
Do not include fields the skill doesn't read or write. Brevity over
completeness — the calling agent loads this into context every time the
skill triggers. The two analyst-v2.2 sub-blocks (Row-level read scope,
Conditional UI states) are likewise opt-in: include them only when the
model declares the rule AND the rule bears on the task. An entity in the
slice whose select_rule doesn't affect the task's read path, or whose
field-level input_type_rule triggers fall outside the task, omits the
sub-block.
Recipe section
Every model write is a recipe. Each recipe:
- Starts with the reads it needs (parallel-fetch if independent)
- Branches on conditions explicitly (refuse / no-op / proceed)
- Writes paired fields in one call (never split related writes)
- Verifies the write took effect (read-back via
--single) - Has an explicit exit-code contract
Use the patterns from use-semantius's response-handling section:
--single for exactly-one reads, default array for zero-or-many.
Runtime disagreement clause (mandatory)
Every generated SKILL.md ends with this clause (adapt the wording):
## When the runtime disagrees with this slice
This skill bakes in the model's shape as of <generation date>. If a write
fails with 409 / 422 / a missing-column / a missing-FK error, the live
schema has drifted from the slice. Abort. Run `read_entity` / `read_field`
on the affected entity, surface the drift to the user, and recommend
regenerating this skill against the current model. Do not silently adapt
the recipe to the new shape — that masks real schema changes the user
should see.
Authoring patterns (forked from skill-creator)
Progressive disclosure
A skill's instructions are loaded into context every time it triggers. Keep the SKILL.md lean. If it grows past ~500 lines, split:
SKILL.md— the workflow and the slicereferences/<topic>.md— details loaded only when the workflow points at them ("readreferences/dedup-strategy.mdbefore any catalog write")
Imperative form
Prefer "Read the entity. Branch on status. Refuse if released." over "The agent should read the entity and then check the status." Imperative voice is shorter and clearer.
Explain the why
LLMs follow instructions better when they understand the reason. Instead
of ALWAYS read before writing, write Read before writing because the crud server returns exit 0 with empty array on zero rows — a blind insert followed by a "verification" read will succeed at the protocol layer and corrupt the catalog with duplicates.
Avoid heavy-handed MUST / NEVER blocks unless the rule is genuinely
non-negotiable. Where you do use them, follow with one sentence of why.
Lean prompts
Remove anything not pulling its weight. If you find yourself writing a section "just in case," cut it. The agent is smart and can ask if it needs more.
Theory of mind
The agent invoking the generated skill has not read the model. It only
has the SKILL.md and the user's prompt. Anything the skill needs that
isn't in SKILL.md or won't be in the user's prompt must come from a
semantius call at runtime — so either inline it now or write the call
that fetches it.
Language conventions for bundled scripts
When a generated skill needs a script (under scripts/ or inline in a
recipe), use this preference order:
- Bash for short, linear chains of
semantius callinvocations (≤ ~30 lines, no nontrivial parsing). Bash is the lingua franca of theuse-semantiusreference and stays readable for simple pipelines. - Bun (TypeScript/JavaScript) for anything with branching, JSON
shaping beyond what
jqhandles cleanly, parallel fan-out, retries, or shared helpers across multiple operations.use-semantiusalready advertises Bun as a first-class scripting option for chaining the CLI. - Do not default to Python. Only use Python if the user explicitly asks for it or if a required library exists only in Python.
When in doubt: Bash for one-screen recipes, Bun for anything that would need a second screen. Reach for Python only on explicit request.
Non-determinism patterns (research, judgment, multi-step)
The defining shape of an agentic skill: reason at the top, act at the bottom. The reasoning section may be open-ended; the action section is always a recipe.
Pattern 1: Research-and-record
- Take a query (e.g. "domain = CRM").
- Read existing catalog rows for that query (dedupe baseline).
- Web research for candidates not in the baseline.
- Filter, score, summarize — the reasoning step.
- Show candidates to user for approval.
- For each approved candidate: read-then-create via a deterministic recipe.
- Read back to verify.
Pattern 2: Judgment-then-write
- Read inputs (entity row, supporting context).
- Reason about the right next state / value / target.
- State the proposed change and the reasoning.
- Ask user for confirmation.
- Write via a deterministic recipe.
- Read back to verify.
Pattern 3: Multi-step workflow
- Decompose the workflow into named stages with explicit pre/post-conditions.
- For each stage: read pre-condition → act → verify post-condition.
- Between stages where state is mutated, expose a checkpoint so a re-run resumes from the last completed stage instead of restarting.
In all three: the reasoning section is allowed to be free-form; the recipes at the bottom are tight, with explicit reads and verifies.
Guardrails the maker enforces in every output
- Read before write. Every
create_*is preceded by the matchingread_*to check for duplicates. Use existing IDs if found. - Paired writes go together. Never split fields that are validated together into two API calls.
- No silent adaptation. If the slice and the live schema disagree, abort and surface the drift.
- No hard deletes of business records by default. Soft-deactivate unless the user explicitly opts in.
- Approval gates for writes. Default: one approval per logical unit of work, not one per row.
- Idempotent re-runs. Re-running with the same inputs is safe; document the no-op / update / append behavior explicitly.
- Exit-code contract. 0 = success, 1 = usage/validation, 2 = platform/tool error, 3 = network.
- Link after writes. Output the UI link
https://tests.semantius.app/<module_slug>/<table_name>so the user can verify in the browser. - Visibility callout on row-scoped reads. Every read
against an entity carrying a
select_ruleis preceded by a one-sentence reminder of what every caller withview_permissionsees (the uniform per-row predicate). The reminder lives in the slice block (preferred) or inline in the recipe (when the slice is omitted). Generated skills that read a scoped entity without naming the scope leave the calling agent unable to tell "no matching rows" from "rows exist but are filtered out for me", and the conclusion downstream is silently wrong. The reminder never promises aview_all_<plural>-style permission bypass; that mechanism is not in the platform spec. When the model's §7 has resolved a documented broadening mechanism, the reminder may point at it; otherwise broader access is human escalation, full stop. - Paired side-effect for
input_type_rule-triggered required fields. When the slice'sConditional UI statesblock records a field whose UI rule flips torequiredon a transition the recipe drives, the PATCH body sets both the trigger field and the dependent field in the same call. The UI would have prompted for the dependent at form time; the recipe has no form and must carry the value itself.
What this skill does NOT do
- Does not auto-derive jobs from the model (that's
semantius-skill-maker). - Does not bundle multiple skills into an A2A agent card.
- Does not deploy the model to a live instance (that's
semantic-model-deployer). - Does not run a formal eval loop in v1 (qualitative review only). Can be added later if you want quantitative benchmarking.
- Does not write skills for non-Semantius backends.
Re-running on an updated model
If the user updates the model after the skill was generated, re-run this maker against the new model and the same task. The output replaces the prior SKILL.md (snapshot the prior version first if the user wants diff-able history). Do not try to "patch" an existing generated skill in place — the slice may have shifted in ways a patch can't safely capture.
First-time invocation checklist
When the user first asks you to build a skill, do this in order:
- Confirm the model path. Read it.
- Confirm the task in one sentence.
- Interview (Step 2 above) — get the answers you need.
- Propose the slice and the shape; let the user correct.
- Propose the generated skill name and output path; let the user override.
- Draft the SKILL.md. Self-review (Step 6).
- Show the user. Iterate.
Do not skip the interview. Skills written without it are usually wrong on the approval gates, the idempotency rule, or the slice — and those are the hard parts to fix later.