# matrx-ai skills

Markdown-backed agent capability library. The runtime layer that consumes
the `skl_definitions` / `skl_categories` / `skl_resources` tables and exposes
the unified `skill` tool (action=list|get|search) via the `agent-skills`
capability bundle. The tool travels WITH the skills, never alone: clients
never declare the capability — the host's `apply_unified_tools` appends it
server-side ONLY when the agent/request skill_config has included or
listed skills (and isn't disabled). An agent with no skills configured
gets no skill tool, no preamble, and no mention of skills anywhere.
Mid-conversation attaches on a frozen system prefix are delivered
ephemerally beside the current user turn by the host's
`_apply_frozen_turn_skills` (see aidream skill_merge.py).

Plan: `/root/.claude/plans/that-is-really-good-snuggly-teapot.md` (or in-repo
copy at `docs/agent-skills-plan.md` when surfaced).
Tool system principles this package mirrors: `docs/official/tool_system_rules.md`.

---

## The two-input rule

Every skill-visibility decision comes from exactly two facts:

1. The skill is `is_active=true` in `skl_definitions`.
2. The agent's `agx_agent.skill_config` permits it (not in `forbidden`).

If a third input creeps in, push back. Either fold it into one of the two
or don't ship it.

## Per-agent tiers

`agx_agent.skill_config` is JSONB with shape:

```json
{
  "included":  ["<uuid>", ...],  // full body baked into system prompt
  "listed":    ["<uuid>", ...],  // name + description only
  "forbidden": ["<uuid>", ...],  // hidden everywhere
  "disabled":  false             // kill switch — skip preamble + tools
}
```

All UUIDs reference `skl_definitions(id)`. The `disable_auto_invocation`
field lives on the skill itself (not on the agent), and prevents an
included skill's `allowed_tools` from being merged into the agent's tool
surface at request time.

## Lifecycle of a request

```
agx.load_for_execution
        │
        ▼  (_row_to_config reads skill_config JSONB)
AgentConfig.skill_config  ←─── SkillConfig parsed defensively
        │
        ▼
apply_unified_skills (aidream/services/tooling/skill_merge.py)
   ├── resolve_skills_for_agent → ResolvedSkills
   ├── add included.allowed_tools UUIDs to config.tools
   └── prepend rendered preamble to first system message
        │
        ▼
apply_unified_tools  (capability + surface + user resolution)
        │
        ▼
Agent runs. skill_get / skill_list / skill_search live in the
conversation as tool-result messages — NEVER edited back into the
system prompt mid-flight.
```

## Cache invariant (A1)

The system prompt is composed **once per request, at request start, and
never mutated mid-flight.** The preamble is byte-stable for a given
agent + skill set, so provider prompt caches keep hitting on subsequent
turns. Mid-flight `skill_get` calls return their content as tool-result
messages — they live in conversation history.

## Hard rules summary

* **SK-R1** — `skill_config` JSONB only carries the four fields above.
* **SK-R3** — no code-location columns on `skl_definitions`. Skills are
  content; tools are code. Linkage happens via `allowed_tools` UUIDs.
* **SK-R4** — no liveness columns. Runtime presence is request-scoped.
* **SK-R10** — every id everywhere is a UUID. No name-based references
  in `skill_config` or `allowed_tools`.
* **SK-R15** — `validate_allowed_tools_on_startup` crashes server boot
  if any `skl_definitions.allowed_tools` UUID doesn't resolve to a real
  `tool_def`. Downgrade to warning with `AIDREAM_STRICT_SKILLS=0`.

## Sandbox auto-discovery

When `resolve_and_arm_run` resolves a sandbox binding for an agent
request (`aidream/api/utils/sandbox_autobind.py`), it schedules a
fire-and-forget background task that walks the sandbox filesystem for
SKILL.md files and upserts them into `skl_definitions`. Same body-hash
idempotency as the local host-path walker — re-running on a different
turn skips unchanged skills.

Mechanism:

1. `ingest_filesystem(roots_or_binding, ...)` dispatches on the input
   type. Pass a `SandboxBinding` (the dataclass from
   `matrx_ai.tools._sandbox_proxy`) and it routes through
   `walk_via_proxy` instead of the local FS walker.
2. `walk_via_proxy(binding)` issues `fs_list` / `fs_read` calls to the
   orchestrator's HTTP proxy. Six conventional roots are probed:
   `.claude/skills`, `.cursor/skills`, `.agent/skills`,
   `.agents/skills`, `.matrx/skills`, `skills/`. Worktree paths are
   excluded.
3. A tracker on `cx_conversation.metadata["skills_ingested_for"]`
   prevents re-ingest on the same (conversation, sandbox) pair — only
   set on success so failed sandbox calls retry next turn.
4. On completion the task emits `RESOURCE_CHANGED kind="skills.ingested"`
   with created / updated / unchanged / roots counts. The matrx-frontend
   stream pump dispatches this into the skills slice, which auto-reloads
   the list and toasts the user.

Errors never raise into the request path — `vcprint(color="yellow")` on
failure.

## File map

```
matrx_ai/skills/
├── __init__.py          # public API
├── models.py            # SkillConfig, SkillHint, SkillBody, ResolvedSkills
├── providers.py         # SkillProvider protocol + DbSkillProvider
├── resolver.py          # resolve_skills_for_agent — request-time entry
├── preamble.py          # render_preamble — system-prompt segment
├── startup_check.py     # SK-R15 validator
└── ingest.py            # filesystem → DB upsert + sandbox auto-discovery

matrx_ai/tools/implementations/skill.py   # skill_list / skill_get / skill_search
matrx_ai/capabilities/built_in.py         # agent-skills capability bundle

aidream/services/tooling/skill_merge.py          # apply_unified_skills
aidream/api/utils/sandbox_autobind.py     # _auto_discover_skills hook + RESOURCE_CHANGED emit
aidream/api/routers/skills.py             # /api/skills CRUD + /ingest + project associations
aidream/db/skl_managers.py                # sklm domain bundle (definitions, categories, resources, skill_projects)
scripts/ingest_skills.py                  # CLI for filesystem ingest

db/migrations/0092_skill_config_and_registry.sql
db/migrations/0094_skl_skill_projects.sql         # many-to-many skill ↔ ctx_projects
db/migrations/0095_agx_agent_skill_config_check.sql  # structural CHECK on skill_config
```

## What's deferred

* Per-repo activation toggles (Phase 4) — design TBD with bidirectional
  tool↔skill linking.
* Filesystem write-back / `sync: true` (Phase 4).
* Semantic / RAG search mode (Phase 5).
* `skl_render_definitions` / `skl_render_components` UI block templates.
* `trigger_patterns`-driven auto-invocation.
