Browse docs

Skill Architecture — Fundamentals + Stack Specialization

Purpose: Canonical explanation of how coding-os organizes skills. Two-layer model: agent-agnostic fundamentals in src/core/skills/ + stack-specific specializations in src/templates/<stack>/skills/. A stack skill declares depends_on: [fundamentals...] in its frontmatter so the agent loads both and gets DRY guidance that specializes where it matters.

Read when: adding a new stack · adding a new skill · debugging why enforce-skill.sh loaded the wrong skill · asking "is this a shared concern or a stack-specific one?".

Nav: Section Index | Docs Index

Aligned with: Claude Certified Architect Foundations (TS 3.2 — skill frontmatter composition; TS 3.3 — path-scoped rules).

The problem composition solves

Before extraction, src/templates/django/skills/python-django/SKILL.md, src/templates/fastapi/skills/python-fastapi/SKILL.md, and src/templates/go-fiber/skills/go-fiber/SKILL.md each restated the same cross-cutting patterns: service/selector split, error envelopes, idempotency, N+1 avoidance, migration discipline, auth middleware shape. Seven backend concerns × three stacks = 21 copies that must stay in sync when any one pattern evolves.

Composition pulls those concerns into one SSOT (src/core/skills/backend-fundamentals/SKILL.md) and leaves each stack skill to cover only what's framework-specific: Django Meta, FastAPI Depends, Fiber middleware chain, etc.

The two layers

src/core/skills/                              ← agent-agnostic, stack-agnostic
├── clean-code/SKILL.md                   ← fail-closed errors, no PII in logs, typed exceptions
├── thinking_os/SKILL.md                  ← Complexity Gate, Zoom cycle (always-active via rule)
├── codebase-explorer/SKILL.md            ← trace-flow, dependency-map
├── backend-fundamentals/SKILL.md         ← services, idempotency, envelopes, migrations, N+1
└── frontend-fundamentals/SKILL.md        ← loading/error/empty states, hydration, a11y, SEO

src/templates/<stack>/skills/                 ← framework-specific extensions
├── django/skills/python-django/          ← Django ORM, DRF, Celery, services+selectors layout
├── fastapi/skills/python-fastapi/        ← Pydantic, Depends, async handlers
├── go/skills/go-patterns/                ← idiomatic Go, error wrapping, table-driven tests
├── go-fiber/skills/go-fiber/             ← Fiber v2 handlers, middleware, validator
└── nextjs/skills/{nextjs-react,frontend-design}/  ← RSC, hydration, design system

How composition works

Each stack SKILL.md declares its dependencies in frontmatter:

---
name: python-django
description: Use when creating or modifying Python files in src/backend/ — Django models, DRF views, services, selectors, Celery tasks, migrations, tests.
globs: "src/backend/**/*.py"
context: fork
depends_on:
  - clean-code
  - backend-fundamentals
allowed-tools:
  - Read
  - Grep
  - Glob
  - Edit
  - Write
---

When enforce-skill.sh matches a file to python-django, the agent loads python-django AND transitively loads every depends_on entry. The resulting context is: universal code quality (clean-code) + cross-stack backend patterns (backend-fundamentals) + Django specifics (python-django). No duplication at source; one skill per task at load time.

Why this beats install-time concatenation: install-time composition (pre-concatenating backend-fundamentals into python-django) would save one skill-load round-trip but breaks skill independence. If the agent is reviewing a non-Django backend project and needs backend-fundamentals alone, it can still load it standalone.

Decision rule — shared vs specific

When adding a rule, ask: does this hold for every stack, or only for ?

Rule class Goes in
Code quality, fail-closed errors, typed exceptions src/core/skills/clean-code
Service/selector split, idempotency, envelopes, N+1 avoidance, migration discipline src/core/skills/backend-fundamentals
Client/server component split, hydration safety, loading-error-empty-state pattern, SEO basics src/core/skills/frontend-fundamentals
Django Meta.ordering, DRF serializer Meta.fields src/templates/django/skills/python-django
FastAPI Depends() chains, Pydantic model_validator src/templates/fastapi/skills/python-fastapi
Next.js App Router conventions, use client rules src/templates/nextjs/skills/nextjs-react

If a rule starts stack-specific and later turns out to hold across stacks, promote it up (move to fundamentals, remove from the specific skill, bump the depends_on only if it wasn't already there).

Why not one monolithic skill per stack (status quo before 2026-04-18)

  • Duplication grows linearly with stack count. 5 backend stacks × 1 monolith each × N shared concerns = 5N copies to update when a concern changes.
  • Cross-stack reviews suffer. Reviewing a go-fiber codebase while having python-django worldview loaded produces noisy suggestions.
  • Testing is weaker. No way to test "does our shared backend guidance hold" — only per-stack monoliths exist.

Why not install-time concatenation (Option B from analysis)

  • Install-time requires a regen step. Miss the step and stack skills drift from src/core/skills/backend-fundamentals.
  • Breaks skill independence — can't invoke backend-fundamentals alone.
  • make regen-rules and make regen-adapter-templates already exist; adding a third regen step raises the bar for contributors.

Runtime composition via depends_on gives the same DRYness with zero regen step.

Interaction with enforce-skill.sh

The hook reads src/core/rules/skill-enforcement.md — a table mapping file globs to primary + secondary skills. That table is generated from src/templates/*/stack.yaml by make regen-rules. Today the table lists:

Globs Primary Secondary
src/backend/**/*.py python-django clean-code
src/backend/**/*.py python-fastapi clean-code, api-design
src/backend/**/*.go go-patterns clean-code
src/backend/**/*.go go-fiber clean-code
src/frontend/**/*.{ts,tsx} nextjs-react clean-code, frontend-design

Once backend-fundamentals and frontend-fundamentals ship, the stack.yaml files should declare them as additional secondaries (or the stack skills declare depends_on: and the hook resolves transitively). This keeps skill-enforcement.md readable while the full dependency graph is computed by the hook.

Public skill standard + trusted import (TASK-369)

Anyone can author or import a skill; the standard keeps the catalog safe and coherent. Three trust tiers:

Tier Origin Scripts execution
core ships in src/core/skills/ trusted
stack ships in src/templates/<id>/skills/ trusted
community imported via cos skill add <path|git-url> into ~/.coding-os/skills/ requires recorded consent (cos skill consent <name>)

Authoringcos skill new <name> scaffolds a spec-compliant skill (frontmatter: name, description ("Use when …" + triggers), tier, domain[], globs, optional depends_on[]/context/license, body with purpose/when-to-read/anti-patterns); cos skill lint <path> validates it through the SAME loader the runtime uses (schema + enum checks), so lint-pass ⇒ loadable. The scaffold passes lint out of the box.

Import gatecos skill add runs, in order:

  1. Normalize: a vanilla Agent-Skills-format skill (name+description only) gains the coding-os taxonomy fields (tier/domain/globs defaults when absent). The taxonomy tier: describes WHAT the skill is; TRUST is recorded in provenance only — a file can never claim a trust level.
  2. Security scan: SKILL.md + scripts//assets/ are pattern-scanned for exfiltration and destructive shapes (remote POSTs of env/keys, piped shell-from-curl, base64+eval, rm -rf /, reverse-shell idioms). Findings BLOCK the import and are printed by name — this is defense-in-depth and honest about being static; consent (below) is the second layer.
  3. License check: a license frontmatter field or LICENSE file is recorded; absence is a warning, not a block.
  4. Provenance: <name>/.provenance.json records source, sha256 of every file, import date and trust tier — cos skill list surfaces it.
  5. Consent: community skills with scripts/ install with scripts_consent: false; executing their scripts is forbidden until cos skill consent <name> records explicit consent.

A community skill may never shadow a core or stack skill name.

Per-project extras (TASK-370).coding-os.yaml::extra_skills is the SSOT (written by init/wizard; no second file). cos skill enable|disable <name> mutates it and, for community skills, maintains symlinks in every installed adapter's skills dir (core/stack skills are already wholesale-linked by the adapter install). cos skill project lists stack-provided vs extra with provenance. The Hub Config → Skills tab toggles the same list via PATCH /api/config/skills/{name} — one shared mutator (cli.skill_commands.set_project_skill) backs both. cos update never prunes a symlinked skill dir: it's an enable-managed link, and descending through it would delete the user's source files.

Per-stack skill groups — onboarding SSOT (TASK-352)

The onboarding wizard ("which skills will this stack install?") and the Config tab need a per-stack grouping that is distinct from the taxonomy tier: in SKILL.md frontmatter. The grouping is derived, never duplicated — there is no extra data file:

Group Derivation (all from stack.yaml + SKILL.md frontmatter)
required primary_skill + skills: (what the stack explicitly declares)
recommended union of skill_enforcement[].secondary minus required
optional remaining core skills (universal catalog)

Single SSOT function: cli.skills_list.collect_stack_skill_groups(stack_id) — consumed by BOTH cos skills-list --stack <id> and the hub endpoints GET /api/hub/stacks/{id}/skills (grouped) / GET /api/hub/skills (global core+stack catalog with provenance: core|stack:<id> and schema validated flag). Each entry carries {name, tier, domain, description, provenance, validated}; a skill a stack references but no skills dir ships yet appears with validated: false and an empty description — visible, not silently dropped. Skill profile resolution order: the stack's own src/templates/<id>/skills/ first, then src/core/skills/.

Path-scoped rules (distinct from skills)

A skill is invoked on demand (Skill skill: "python-django"); a rule loads automatically based on file path. For universal policies (e.g., all Python uses ruff), prefer a rule in src/core/rules/ with paths: ["**/*.py"] frontmatter over a skill load. The exam guide's TS 3.3 favors path-scoped rules for "conventions that span multiple directories regardless of stack" — rules < skills in context cost, so pick rules when the policy is tiny and universal.

Testing a new fundamentals skill

  1. Write the skill with a clear description that says WHEN to use it.
  2. Add depends_on: from at least one stack skill so it's actually exercised.
  3. Manually trigger: edit a relevant file in this repo via an agent; confirm enforce-skill.sh logs fundamentals skill loaded in .coding-os/.hooks.log.
  4. Add a test in tests/test_skills.py (or the skills-specific test file) that loads the SKILL.md and asserts required frontmatter keys.

References