skills/olog-construction/SKILL.md
Build ontology logs (ologs) from problem descriptions using categorical foundations. Use when designing problem taxonomies, classifying tasks for routing, building knowledge libraries, establishing formal analogies between domains via functor search, or translating between natural language and database schemas. NOT for OWL/RDF ontology work, query tuning, or graph modeling without functional-arrow discipline.
npx skillsauth add curiositech/windags-skills olog-constructionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
An olog is a category presented as a labeled graph. Objects are types (labeled with singular indefinite noun phrases). Arrows are functional relationships (labeled with verb phrases that form readable sentences). Path equivalences encode business rules. An olog IS a database schema; its instances are functors to Set.
flowchart TD
A[Incoming request for Olog Construction] --> B{Within this skill's scope?}
B -->|No| C[Redirect using NOT-for boundaries]
B -->|Yes| D[Assess inputs, constraints, and current state]
D --> E{Which path fits best?}
E -->|Plan or design| F[Choose the simplest viable pattern]
E -->|Migration or change| G[Protect compatibility and rollout safety]
E -->|Debug or evaluate| H[Localize the failing boundary first]
F --> I[Apply the domain-specific guidance below]
G --> I
H --> I
I --> J[Validate against the quality gates]
Use this as the first-pass routing model:
Formal definition: An olog is a finite category C where:
Key insight from Spivak (2012): The functional arrow constraint is what makes ologs rigorous. In a knowledge graph, "a person --speaks--> a language" would be a valid edge. In an olog, it is NOT valid because a person may speak multiple languages (the relationship is not functional). You must restructure this as a span (see below).
The rule: Every arrow f: A --> B must satisfy: for each element a in A, there exists exactly one element b in B such that f(a) = b.
Many-to-many relationships require spans. A span is a pair of arrows from an intermediate "relationship object":
a language spoken by a person
/ \
is spoken by is a language
| |
v v
a person a language
The intermediate object "a language spoken by a person" has two functional arrows:
This is equivalent to a junction/bridge table in relational databases. The categorical perspective makes explicit that many-to-many relationships are not primitive -- they decompose into two functional relationships through an intermediary type.
One-to-many relationships (a person has many addresses) also need restructuring:
an address of a person --belongs to--> a person (functional: each address belongs to one person)
an address of a person --is located at--> an address (functional: each record maps to one address)
Path equivalences are commutative diagrams that encode domain invariants. If paths p and q from object A to object Z always produce the same result, write p ~ q.
Example:
a paper --has as first author--> a researcher --works at--> an institution
a paper --was submitted to--> a journal --is published by--> an institution
These paths are NOT equivalent (the first author's institution != the journal's publisher in general).
Declaring them equivalent would be asserting a business rule that papers can only be submitted
to journals published by the first author's institution.
When to declare equivalence:
Validation: For every declared equivalence p ~ q, instantiate with 3-5 concrete examples. If any example produces different results via the two paths, the equivalence is false.
The Grothendieck construction (Spivak 2014, Chapter 7): An olog C defines a database schema. An instance of the olog is a functor I: C --> Set, which assigns:
This means:
Practical consequence: If you can draw a valid olog, you can mechanically derive a normalized relational schema. The categorical structure guarantees referential integrity by construction.
A fiber product (pullback) models "things that share a common attribute." Given:
A --f--> C <--g-- B
The fiber product A x_C B is the set of all pairs (a, b) where f(a) = g(b).
Example:
a person who lives in state S --lives in--> a US state <--is located in-- a university in state S
Pullback: "a person-university pair in the same state"
= {(person, university) | person.state = university.state}
This is exactly a SQL JOIN: SELECT * FROM persons p JOIN universities u ON p.state_id = u.state_id
When to use pullbacks in olog design:
The key application for multi-agent systems: Given two ologs C and D representing different problem domains, a functor F: C --> D establishes a structural mapping between them. If F exists and preserves path equivalences, the domains are formally analogous.
What functor search gives you:
Computational complexity:
Tools:
Read the problem description. Identify every noun phrase that refers to a CLASS of things (not a specific instance).
Test: Can you write "a [noun phrase]" and have it refer to an arbitrary member of the class? If yes, it is a type.
Good types: "a customer", "a product", "a shipping address", "a date"
Bad types: "Amazon" (specific instance), "shipping" (verb/process, not a type),
"the database" (specific instance, not a class)
Common mistake: Too many types. Beginners create types for every noun they see. Instead, ask: "Does this type participate in at least one functional relationship with another type?" If not, it is likely an attribute (a functional arrow to a primitive type like "a string" or "a date"), not a standalone type.
For every pair of types, ask: "Does every [source type] have exactly one [target type] via some relationship?"
Test: For the candidate arrow f: A --> B, can you find ANY element of A that maps to zero elements of B, or to more than one element of B? If yes, f is NOT functional.
Functional: "a person" --has as SSN--> "a SSN" (each person has exactly one)
Not functional: "a person" --has as phone number--> "a phone number" (a person may have 0 or many)
For non-functional relationships, introduce a span (Step 2b).
For every pair of paths from object A to object Z, ask: "Do these paths ALWAYS produce the same result?"
Use concrete examples. If the paths produce different results for any example, they are NOT equivalent.
Run these checks:
LLMs are good at Step 1 (extracting types) and Step 2 (proposing arrows). They are BAD at Step 3 (declaring valid path equivalences) and Step 4 (rigorous validation).
Recommended workflow:
Prompt template for LLM olog proposal:
Given the following domain description, construct an olog:
[description]
For each type, provide:
- A singular indefinite noun phrase label (e.g., "a customer")
For each aspect (arrow), provide:
- Source type
- Target type
- A verb phrase label such that "[source] [label] [target]" is a true sentence
- Confirmation that the relationship is functional (each source element maps to EXACTLY ONE target element)
If a relationship is many-to-many, decompose it into a span with an intermediate type.
List any path equivalences and provide 3 concrete examples demonstrating each equivalence holds.
Problem: An agent coordination system receives tasks. Each task has a domain (e.g., "frontend", "backend"). Each task is assigned to exactly one agent. Each agent has a set of skills. We want to route tasks to agents whose skills match the task's domain.
Olog:
Types:
T = "a task"
D = "a domain"
A = "an agent"
S = "a skill of an agent"
Arrows:
T --belongs to--> D (functional: each task belongs to exactly one domain)
T --is assigned to--> A (functional: each task is assigned to exactly one agent)
S --is possessed by--> A (functional: each skill record belongs to exactly one agent)
S --qualifies for--> D (functional: each skill qualifies for exactly one domain)
Path equivalence (DESIRED, not inherent):
T --is assigned to--> A should satisfy:
there EXISTS some S such that S --is possessed by--> A and S --qualifies for--> T.domain
This is a CONSTRAINT, not a path equivalence. It says: "only assign tasks to agents
who have a qualifying skill." Encode this as a pullback condition.
Pullback:
ValidAssignment = Agent x_Domain Task
= {(agent, task) | agent has a skill qualifying for task.domain}
Derived SQL:
CREATE TABLE domains (id TEXT PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE agents (id TEXT PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
domain_id TEXT NOT NULL REFERENCES domains(id),
agent_id TEXT NOT NULL REFERENCES agents(id)
);
CREATE TABLE skills (
id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL REFERENCES agents(id),
domain_id TEXT NOT NULL REFERENCES domains(id)
);
-- Constraint: task assignment requires matching skill
-- Enforced via trigger or application logic (pullback condition)
Use the anti-pattern catalog below as the operational failure-mode checklist for this skill. During execution, explicitly look for these failure patterns before you ship a recommendation or implementation.
Novice: Creates a type for every noun in the problem description, including attributes that should be arrows to primitive types. Produces an olog with 50+ types where 30 of them are leaf nodes with no outgoing arrows. Expert: Aggressive pruning. If a type has only one incoming arrow and no outgoing arrows, it is probably an attribute (arrow to a primitive type like "a string" or "a number"), not a standalone type. Aim for 8-15 types in a first-pass olog. Expand only when validation reveals missing structure. Timeline: This happens on every first attempt. The cure is the "does this type participate in at least two relationships?" test.
Novice: Labels an arrow "a person --has--> a phone number" without checking functionality. The olog is invalid because a person can have zero or many phone numbers. Expert: Every arrow must pass the functionality test before inclusion. Many-to-many and one-to-many relationships MUST be decomposed into spans. This is not optional -- it is the defining constraint of ologs vs. arbitrary labeled graphs. Timeline: Caught immediately if you run the validation checklist. Missed entirely if you skip validation.
Novice: Declares two paths equivalent because they "seem like they should be" without testing with concrete instances. Example: "a paper's first author's institution = the journal's publisher" -- false in general. Expert: Never declare an equivalence without testing it against at least 3 concrete instances where the answer is known. Path equivalences are the hardest part of olog construction because they encode INVARIANTS, not just relationships. Timeline: This is the most common source of incorrect ologs. LLMs are particularly bad at this -- they hallucinate plausible-sounding equivalences that fail on concrete examples.
Novice: Tries to capture an entire enterprise in a single olog with 100+ types. Expert: Decompose into sub-ologs connected by functors. Each sub-olog should capture one bounded context (in DDD terms). Inter-context relationships are functor mappings, not direct arrows. This mirrors microservice architecture: each service has its own olog, and integration is via typed interfaces. Timeline: Encountered when scaling past toy examples. The fix is always decomposition.
"The arrow is many-to-many" -- There are no many-to-many arrows in an olog. If someone says this, they haven't internalized the functional arrow constraint. The correct response: "Decompose it into a span."
"An olog is just an ER diagram" -- Close but wrong. An ER diagram allows non-functional relationships (many-to-many directly). An olog enforces that EVERY arrow is functional. The olog is more constrained and therefore more compositional. An ER diagram can violate compositionality; an olog cannot.
"Path equivalences are just foreign key constraints" -- Path equivalences are RICHER than foreign key constraints. A foreign key says "this column references that table." A path equivalence says "these two COMPOSITIONS OF foreign keys produce the same result." FK constraints are a special case (paths of length 1).
"Can I compute the functor automatically?" -- For small ologs (< 20 types), yes, exhaustively. For medium ologs (20-50), use CSP. For large ologs, it's NP-hard in general. But practical domain ologs rarely exceed 30 types per bounded context, so this is usually tractable.
When deeper detail is needed on any of these topics, the following primary sources should be consulted:
You have a valid olog when:
data-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.