Skill Storage
Status: Design landing Reference epics: INK-835, INK-829, INK-833, INK-844 ADRs: ADR-016
Skills are part of the workspace, but they are not world content (see Skill System; decision D31). They have their own storage shape, their own permission surface, their own sync scope, and an explicit non-relationship with the submit boundary. This page specifies those pieces.
The sibling pages:
- Skill System — the surrounding framing.
- Skill Composer — the subagent that reads these entities at runtime.
The entity
Section titled “The entity”A skill is a package — not a single content blob. The package has:
- Metadata: id, name, description, version, source (
system|user|imported|marketplacereserved), author, declaredrequired_capabilities,tags, declaredparameter_schema, optionalmodel_preference. - Framing: the authored prose (or
PromptTemplateartifact) the Composer uses as the prompt opening. - Artifacts: zero or more typed supporting resources —
Description,Approach,PromptTemplate,Example. - Invocation contract:
input_schema,output_schema, declared progress-reporting shape, named interrupt points.
Artifact kinds are a closed set. New kinds require an explicit design pass. Retired kinds — DspyModule, and CodeTemplate in its RLM-sandbox sense — are gone. A Resource kind (for attached reference files) is an open question; it is not part of the current design.
Where skills live
Section titled “Where skills live”Skills live in inklings.db (decision D26 — the unified workspace database). They do not live:
- In
agents.db— that database is retired; skill tables live ininklings.db. - In the world-content schema (pages, blocks, entities, etc.). There are no foreign keys from skill tables into world-content tables.
- In the Agent Memory System tiers. Skills are not memory.
The host (Tauri side) owns the tables. The sidecar reads and writes through MCP tools (see MCP System); Rust handlers execute the SQL. This follows the same separation every workspace-adjacent table uses.
SQLite schema
Section titled “SQLite schema”Three tables: skills, skill_artifacts, and skill_permissions.
skills
Section titled “skills”The package’s identity and metadata.
CREATE TABLE skills ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', version TEXT NOT NULL DEFAULT '1.0.0', source TEXT NOT NULL DEFAULT 'system', required_capabilities TEXT NOT NULL DEFAULT '[]', -- JSON array tags TEXT NOT NULL DEFAULT '[]', -- JSON array artifact_manifest TEXT, -- reserved; optional hash for cache invalidation created_at TEXT NOT NULL, updated_at TEXT NOT NULL);CREATE INDEX idx_skills_source ON skills(source);CREATE INDEX idx_skills_version ON skills(version);A StoredMetadata JSON column approach is used for fields outside this base shape (parameter schema, invocation contract, model preference). JSON-blob metadata is the right shape for schema resilience across skill-format revisions.
The source values:
system— bundled with the app.user— authored in this workspace.imported— brought in from a file or another workspace.marketplace— reserved; marketplace browsing and publishing are not implemented.
skill_artifacts
Section titled “skill_artifacts”Typed supporting resources belonging to a skill.
CREATE TABLE skill_artifacts ( id TEXT PRIMARY KEY, skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE, kind TEXT NOT NULL, -- Description | Approach | PromptTemplate | Example name TEXT NOT NULL, content TEXT NOT NULL, summary TEXT, ordering INTEGER NOT NULL DEFAULT 0, estimated_tokens INTEGER, state_blob BLOB, -- reserved; unused post-skill-compiler removal UNIQUE(skill_id, name));CREATE INDEX idx_skill_artifacts_skill ON skill_artifacts(skill_id);The state_blob column is a tombstone — retained to avoid a migration, but it has no writer. The DSPy skill-compilation pipeline that historically wrote this column is retired (decision D5); nothing in the current design reads or writes it.
skill_permissions
Section titled “skill_permissions”The per-workspace permission record for a skill. One row per (skill_id, workspace_id).
CREATE TABLE skill_permissions ( skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE, workspace_id TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1, -- bool channels_allowed TEXT NOT NULL DEFAULT '[]', -- JSON: ["all"] | ["channel_id", ...] autonomous_task_contexts TEXT NOT NULL DEFAULT '[]', -- JSON: category names where the skill is invokable autonomously updated_at TEXT NOT NULL, PRIMARY KEY (skill_id, workspace_id));CREATE INDEX idx_skill_permissions_workspace ON skill_permissions(workspace_id);Semantics:
enabledgates all invocation. A disabled skill is not dispatched even if the caller asks for it by name.channels_allowedrestricts conversation-driven invocations to named channels. The special value["all"]means every channel. An empty array means no channel — conversation-driven invocation is disabled but scheduled-task invocation may still be allowed.autonomous_task_contextsrestricts scheduled-task and agent-selected autonomous invocations. The category model for autonomous tasks is specified in the scheduling subsystem (see Scheduling System and the autonomous-task resolution in the parking lot); this table carries the named references.updated_atdrives sync conflict resolution (LWW).
The permission record is created with sensible defaults on install. Defaults for an imported skill are “enabled: false, channels_allowed: [], autonomous_task_contexts: []” — an import is acknowledged, but permissions are opt-in per workspace. Defaults for a system skill can be “enabled: true, channels_allowed: [‘all’]” if the bundled skill’s manifest declares low-risk use.
The retired execution_traces table (from the DSPy skill-compilation era) is not recreated. Observability lives on the invocation record associated with the turn, not a separate trace table.
Retired tables
Section titled “Retired tables”cached_skills— the v1 single-content table from before multi-artifact. Exits when its last consumer exits.execution_traces/artifact_traces— retired with the DSPy skill-compilation pipeline (D5).agents.db— the whole database retires; tables that belong here move intoinklings.db.
What does not apply
Section titled “What does not apply”Explicitly, for avoidance of doubt:
- No
origincolumn onskillsorskill_artifacts. Skills are not world-model claims. - No
lifecyclecolumn. There is nocandidate→canonical-allowedtransition for skill CRUD. - No foreign keys from skill tables into world-content tables.
- No
derivation_linksrows between skill entities. - No
deviation_recordsrows produced by skill CRUD. - Skill writes do not pass through the submit boundary’s invariant check (see Submit Boundary). They pass through the ordinary repository-layer consistency checks any SQLite write goes through.
The separation is architectural, not just a convention. The Tauri command layer routes skill CRUD to the SkillPackageRepository, which writes directly to these tables. It does not invoke the submit-boundary adapter chain. An implementation that accidentally added a boundary-crossing path would violate decision D31 and the subsystem’s layering.
Import / export
Section titled “Import / export”Markdown with YAML frontmatter is the canonical transport format for skills (see Skill System — canonical authoring format). The SQLite record is a cache and index over the markdown source; the .md file is the portable artifact.
Export
Section titled “Export”A skill exports as a single .md file. For bulk sharing or marketplace distribution, .md files are collected into a .tar bundle — one file per skill, no subdirectories required.
The file structure is:
<skill-name>.md # frontmatter (metadata + contract) + markdown body (framing + artifacts)The frontmatter fields map directly to the skills table columns and the StoredMetadata JSON blob. The markdown body holds the prompt framing and any structured artifact sections. The round-trip is lossless: importing and immediately re-exporting produces an identical file.
There is no skill.json manifest, no framing.md side-file, and no artifacts/ subdirectory. The single .md file is the complete, self-contained representation.
Import
Section titled “Import”import_skill is the application-layer use case. It accepts:
- A skill
.mdfile (or a.tarof.mdfiles for bulk import). - An acknowledged source: the worldbuilder has confirmed the origin and intent. Import is not gated by a draft-like limbo (decision D31).
- An optional capability preview: before the import commits, the UI renders which tools the skill declares it will use and what the caller’s capability set would expose to it. The worldbuilder acknowledges; the import proceeds.
On import:
- The frontmatter is parsed and validated against the current schema (required fields, closed artifact-kind set, known
required_scopesvalues). - The markdown body is parsed into framing prose and any structured artifact sections.
- If a skill with the same
idexists, the import is idempotent-by-id — it overwrites, preserving the localskill_permissionsrow unless the import explicitly carries permissions. - Rows are written to
skills,skill_artifacts, andskill_permissions(with conservative defaults forimportedsource). - The skill appears in the workspace’s skill surface.
The application-layer use case is named import_skill. The marketplace source value is reserved; marketplace-sourced installs route through this same path. Marketplace browsing and publishing are not implemented.
The Supabase-backed sync module (skill_sync) replicates skills across devices for a given workspace.
- All workspace skills sync — not only user-authored. Bundled, user-authored, and imported skills are all in scope.
skill_artifactssyncs alongsideskills. An artifact change is part of the skill’s state; it participates in the same LWW conflict-resolution byupdated_aton the parentskillsrow.skill_permissionssyncs across devices. A skill enabled on the worldbuilder’s laptop is enabled on the worldbuilder’s desktop for the same workspace. LWW on the permissions row’s ownupdated_at.- LWW-by-
updated_atis the conflict resolution strategy. marketplace_skill_refsschema capacity is reserved. Marketplace browsing and publishing are not implemented.
What sync covers vs. does not
Section titled “What sync covers vs. does not”- Covers: per-workspace-cross-device. A skill present in workspace W on device A syncs to device B when B opens W.
- Does not cover: cross-workspace following. Whether installing a skill in workspace A automatically exposes it in workspace B is a separate question. Currently, installing in A affects only A.
What sync does not promote
Section titled “What sync does not promote”Sync is orthogonal to permissions. A skill arriving on a new device does not silently enable itself in channels that were not enabled before. The permissions row syncs too; its enabled state governs invocability on every device.
Relationship to agent_memory
Section titled “Relationship to agent_memory”Skills are not agent memory. The four-tier Agent Memory System does not store skills, skill artifacts, or skill permissions. A skill’s invocation reads memory through MCP memory tools the same way any turn does; the skill definition itself lives in these storage tables, separate from the memory tiers.
If language elsewhere in the Codex implies skills persist inside agent_memory, that language is superseded by this page.
Relationship to provenance and the submit boundary
Section titled “Relationship to provenance and the submit boundary”Repeated for emphasis, because the prior D18 framing said the opposite:
- Skill CRUD does not cross the Submit Boundary.
- Skills do not carry Provenance.
- No Derivation Links between skill entities.
- No Deviation Records produced by skill CRUD.
- Content produced by a skill during execution crosses the boundary with full provenance, same as any agent write (decision D20).
Relationship to other systems
Section titled “Relationship to other systems”- Skill System — the surrounding framing.
- Skill Composer — the runtime subagent that reads these entities.
- MCP System — the transport for skill-management tools (
create_skill,update_skill,list_skills,add_artifact) the sidecar uses to call back into the Rust domain. - Agent Memory System — distinct; skills are not a memory tier.
- Scheduling System — consumes
skill_permissions.autonomous_task_contextswhen evaluating whether a scheduled task may invoke a skill. - Permission System — capability resolution at invocation time; independent of
skill_permissions(the latter gates whether the Composer is dispatched at all; the former narrows the tool surface once dispatched).
Open questions
Section titled “Open questions”- Artifact kind set. Whether to add
Resource(generic attached reference file — image, data table, small binary) is a design call for a future pass. Worldbuilding skills that bundle reference material are the motivating use. - Cross-workspace skill following. Whether a skill installed in workspace A should follow the author to workspace B. Deferred; per-workspace-cross-device is what the current subsystem commits to.
- Permission granularity. Whether
channels_allowedshould permit per-channel-type (e.g., “all research channels”) as well as per-channel-id. Current schema supports per-id +"all"; richer forms are a schema addition, not a breaking change. - Marketplace storage shape. Whether marketplace-sourced skills get a separate table (
marketplace_skill_refsis reserved) or reuseskillswithsource='marketplace'and additional metadata. Not resolved; marketplace is not implemented.
What this page does not do
Section titled “What this page does not do”- It does not describe the composition algorithm or its I/O contract. See Skill Composer.
- It does not describe the authoring UI. That is a frontend concern.
- It does not specify the Tauri command or HTTP bridge signatures. Those mirror the application-layer CRUD and are documented alongside other Tauri commands.
- It does not describe autonomous-task categorization in detail. See Scheduling System and the autonomous-task-category work tracked in the parking lot.
Was this page helpful?
Thanks for your feedback!