Better Skills models the knowledge base as a directed graph where skills and resources are nodes, and links are typed edges. The graph spans multiple vaults and supports polymorphic connections.
Graph Structure
Skills are primary nodes
Each skill in the database is a node in the graph, identified by its UUID.
Resources are sub-nodes
Resource files (references, scripts, assets) are child nodes under their parent skill.
Links are directed edges
skill_link rows represent typed, directed connections between any pair of nodes.
Node Types
Skills as Nodes
Every skill is a graph node with:
Identity : UUID primary key
Vault scope : Belongs to exactly one vault
Metadata : Name, description, frontmatter
Content : SKILL.md markdown
Child nodes : Zero or more resource sub-nodes
Resources as Sub-Nodes
Resources are nested nodes within their parent skill:
CREATE TABLE skill_resource (
id UUID PRIMARY KEY ,
skill_id UUID NOT NULL REFERENCES skill(id) ON DELETE CASCADE ,
path TEXT NOT NULL ,
kind skill_resource_kind NOT NULL , -- reference | script | asset | other
content TEXT NOT NULL ,
UNIQUE (skill_id, path )
);
Reference Markdown documentation files under references/.
Script Executable code samples under scripts/.
Asset Binary or config files under assets/.
Other Miscellaneous files that don’t fit other categories.
Resources are cascade deleted when their parent skill is deleted. They cannot exist independently.
Edge Structure: skill_link
The skill_link table represents polymorphic directed edges between nodes:
export const skillLink = pgTable ( "skill_link" , {
id: uuid ( "id" ). defaultRandom (). primaryKey (),
// Source: exactly one of these
sourceSkillId: uuid ( "source_skill_id" ). references (() => skill . id , { onDelete: "cascade" }),
sourceResourceId: uuid ( "source_resource_id" ). references (() => skillResource . id , { onDelete: "cascade" }),
// Target: exactly one of these
targetSkillId: uuid ( "target_skill_id" ). references (() => skill . id , { onDelete: "cascade" }),
targetResourceId: uuid ( "target_resource_id" ). references (() => skillResource . id , { onDelete: "cascade" }),
// Edge metadata
kind: text ( "kind" ). notNull (). default ( "related" ),
note: text ( "note" ),
metadata: jsonb ( "metadata" ). notNull (). default ( sql `'{}'::jsonb` ),
createdByUserId: text ( "created_by_user_id" ). references (() => user . id , { onDelete: "set null" }),
createdAt: timestamp ( "created_at" ). defaultNow (). notNull (),
});
Polymorphic Source and Target
Each link has:
Exactly one source : Either source_skill_id OR source_resource_id
Exactly one target : Either target_skill_id OR target_resource_id
This enables four link shapes:
Skill → Skill
Skill → Resource
Resource → Skill
Resource → Resource
INSERT INTO skill_link (source_skill_id, target_skill_id, kind)
VALUES ( 'turborepo-uuid' , 'next-best-practices-uuid' , 'related' );
Database constraints enforce that exactly one source and one target are set. Attempting to set both source_skill_id and source_resource_id will fail.
Link Types
The kind field categorizes the relationship:
Kind Description Typical Use relatedGeneral association Topically connected skills depends_onFunctional dependency Skill requires another skill’s context mentionMarkdown reference Auto-generated from [[skill:uuid]] mentions extendsInheritance relationship Skill builds on another skill supersedesVersion replacement Newer skill replaces older one
The mention kind is automatically created when markdown content contains [[skill:uuid]] or [[resource:uuid]] syntax. See Mentions for details.
Each link can store additional context:
interface LinkMetadata {
origin ?: "markdown-auto" | "manual-ui" | "api-import" ;
confidence ?: number ; // For auto-detected links
context ?: string ; // Surrounding text snippet
[ key : string ] : unknown ;
}
Auto Links vs Manual Links
Auto Links Created automatically from markdown mentions.
metadata.origin: "markdown-auto"
Recreated on skill update
Deleted when mention removed
Manual Links Explicitly created by users via UI or API.
metadata.origin: "manual-ui"
Persist independently of markdown
Can have custom notes and types
Duplicate edges are allowed by design. A skill can have multiple links to the same target with different kind values or metadata.
Graph Traversal
Outgoing Links (Dependencies)
Find all skills and resources this skill links to:
SELECT
sl . kind ,
sl . note ,
COALESCE ( target_skill . name , target_resource . path ) AS target_name
FROM skill_link sl
LEFT JOIN skill target_skill ON sl . target_skill_id = target_skill . id
LEFT JOIN skill_resource target_resource ON sl . target_resource_id = target_resource . id
WHERE sl . source_skill_id = 'my-skill-uuid' ;
Incoming Links (Dependents)
Find all skills and resources that link to this skill:
SELECT
sl . kind ,
COALESCE ( source_skill . name , source_resource . path ) AS source_name
FROM skill_link sl
LEFT JOIN skill source_skill ON sl . source_skill_id = source_skill . id
LEFT JOIN skill_resource source_resource ON sl . source_resource_id = source_resource . id
WHERE sl . target_skill_id = 'my-skill-uuid' ;
Multi-Hop Traversal
Recursive queries can traverse the graph across multiple hops:
WITH RECURSIVE skill_graph AS (
-- Base: direct links from starting skill
SELECT
target_skill_id AS skill_id,
1 AS depth
FROM skill_link
WHERE source_skill_id = 'starting-skill-uuid'
AND target_skill_id IS NOT NULL
UNION ALL
-- Recursive: follow links from previously found skills
SELECT
sl . target_skill_id ,
sg . depth + 1
FROM skill_graph sg
JOIN skill_link sl ON sg . skill_id = sl . source_skill_id
WHERE sl . target_skill_id IS NOT NULL
AND sg . depth < 3 -- Limit depth
)
SELECT DISTINCT s . name , sg . depth
FROM skill_graph sg
JOIN skill s ON sg . skill_id = s . id
ORDER BY sg . depth , s . name ;
Graph traversal respects vault boundaries. Links can reference skills in any vault the user has access to.
Cascade Deletion
Graph integrity is maintained through cascade rules:
Deleting a skill
All child resources are deleted (ON DELETE CASCADE)
All incoming and outgoing links are deleted
Deleting a resource
All incoming and outgoing links from that resource are deleted
Parent skill is unaffected
Deleting a user
created_by_user_id in links is set to NULL (ON DELETE SET NULL)
Links themselves are preserved
Same-Vault Validation
When creating links from markdown mentions, the system enforces same-vault constraints :
Validation Logic
Valid Mention (Same Vault)
Invalid Mention (Cross-Vault)
// From packages/api/src/lib/link-sync.ts
const sourceVaultId = await getVaultIdForNode ( sourceSkillId , sourceResourceId );
const targetVaultId = await getVaultIdForNode ( targetSkillId , targetResourceId );
if ( sourceVaultId !== targetVaultId ) {
throw new Error (
`Cross-vault mentions not allowed: source in ${ sourceVaultId } , target in ${ targetVaultId } `
);
}
Manual links created via UI/API can span vaults. The same-vault rule applies only to markdown-auto mentions .
Graph API
The tRPC API exposes graph operations:
// Get graph centered on a skill
const graph = await trpc . skills . getGraph . query ({
skillId: 'my-skill-uuid' ,
depth: 2 , // How many hops to traverse
includeResources: true ,
});
// Graph response structure
interface SkillGraph {
nodes : {
skills : { id : string ; name : string ; vaultId : string }[];
resources : { id : string ; path : string ; skillId : string }[];
};
edges : {
id : string ;
source : { type : 'skill' | 'resource' ; id : string };
target : { type : 'skill' | 'resource' ; id : string };
kind : string ;
note ?: string ;
}[];
}
Next Steps
Mentions Learn how markdown mentions create auto links
Skills Understand skill structure and resources
Vaults Review vault types and permissions