Automated summary
Enables querying janus-datalog databases using EDN queries for debugging and data exploration.
Strengths
- Supports powerful Datomic-style EAV queries with joins, aggregations, and predicates
- Works with CRDT-backed storage, including time-travel queries via history and as-of
- Provides a simple CLI tool that outputs markdown tables for readability
- Can export entire databases to EDN for full inspection
Limitations
- Requires understanding of datalog query syntax and the EAV data model
- Only works with janus-datalog databases, not general SQL or other DBs
- Large exports may be impractical for very big databases
Use when you need to inspect, debug, or explore a janus-datalog database, especially one using CRDT state.
Do not use for querying SQL databases or non-datalog data stores; prefer native tools for those systems.
Security analysis
SafeThe skill uses a specific local CLI tool (datalog) from a known GitHub repository, with no network activity or destructive operations. The command is constrained to only that binary, and injection risk is minimal because the agent constructs EDN queries based on user intent. The -export flag could write to arbitrary paths, but only when explicitly instructed.
No concerns found
How this analysis is madeExamples
Explore the database at /path/to/db.db and list all attributes and their sample values.Run a datalog query to find all entities with a :person/name attribute and their ages from the database at db.db.Get the full history of datoms for entity 42 in the database at test.db, showing all past values.name: datalog description: > Query janus-datalog databases for debugging and data exploration. Use when the user asks to inspect a .db database, explore datoms, check entity attributes, debug query results, or examine CRDT storage state. argument-hint: <database-path> allowed-tools: Bash(~/go/bin/datalog *)
Datalog Query
Query janus-datalog databases for debugging and data exploration.
The user will provide a database path and describe what they want to know. Use the datalog CLI to run queries against the database.
Data Model
janus-datalog is a Datomic-style EAV (Entity-Attribute-Value) store. All data is stored as datoms: [entity attribute value tx]. There are no tables — entities are just collections of attribute-value pairs, connected by shared entity IDs.
Attributes follow a namespaced keyword convention: :namespace/name (e.g., :person/name, :order/total). This means attributes cluster by namespace, which is useful when exploring unknown databases.
Storage is CRDT-backed (Conflict-free Replicated Data Types). Every write is preserved with a unique ElementID. Attribute cardinality determines conflict resolution:
| Cardinality | Behavior | Example |
|-------------|----------|---------|
| One | Last-Writer-Wins — only the latest value is returned by default | :person/name, :person/age |
| Many | Add-Wins Set — all distinct values are returned | :person/tag, :person/role |
| Vector | RGA — ordered list with deterministic merge | :product/tags, :playlist/songs |
Because all writes are preserved, you can time-travel: db.History() returns all raw datoms (no CRDT resolution), and db.AsOf(elementID) queries CRDT-resolved state as of a specific transaction.
Tool
Binary: ~/go/bin/datalog
Install if missing:
go install github.com/wbrown/janus-datalog/cmd/datalog@latest
Invocation
# Run a query
~/go/bin/datalog -db <path> -query '<EDN query>'
# With input bindings (repeatable, one per :in parameter after $)
~/go/bin/datalog -db <path> -query '<EDN query with :in>' -in '<EDN value>' [-in '<EDN value>' ...]
# With performance annotations
~/go/bin/datalog -db <path> -verbose -query '<EDN query>'
# Export entire database to readable EDN (useful for small DBs)
~/go/bin/datalog -db <path> -export <output.edn>
Output is a markdown table. Errors go to stderr.
If the user says /datalog <path>, treat $ARGUMENTS as the database path. Start by discovering what's in the database, then ask what they want to explore.
Finding Databases
- Prebuilt benchmark DB:
datalog/storage/testdata/ohlc_benchmark.db - Default path when unspecified:
datalog.dbin current directory - Test databases are created in temp directories by tests (look for
t.TempDir()calls) - Ask the user if the path is unclear
EDN Query Syntax
Queries are EDN vectors. The basic form:
[:find <find-spec>
:in <input-spec> ;; optional, $ is implicit
:where <clauses>]
Find Spec
;; Return specific variables
[:find ?name ?age :where ...]
;; With aggregations (non-aggregated vars become group-by keys)
[:find ?dept (sum ?salary) (count ?e) :where ...]
;; Available: sum, count, avg, min, max
Where Clauses
Data patterns — match entity-attribute-value triples:
[?entity :attribute ?value] ;; basic pattern
[?entity :attribute "literal"] ;; match literal value
[?entity :attribute _] ;; wildcard (attribute exists)
[?entity :attribute ?value ?tx] ;; with transaction ID
Predicates — filter results:
[(> ?age 25)]
[(< ?price 100.0)]
[(!= ?name "Alice")]
[(< 0 ?x 100)] ;; chained: 0 < ?x < 100
Expressions — compute values:
[(+ ?age 5) ?future-age]
[(- ?price ?discount) ?net]
[(* ?qty ?price) ?total]
[(str ?first " " ?last) ?fullname]
[(ground 42) ?answer]
[(identity ?x) ?y] ;; pass-through binding
Time extraction — extract components from time.Time values:
[(year ?timestamp) ?y]
[(month ?timestamp) ?m]
[(day ?timestamp) ?d]
[(hour ?timestamp) ?h]
[(minute ?timestamp) ?min]
[(second ?timestamp) ?sec]
NOT clauses — exclude matches:
(not [?p :person/deleted true])
(not-join [?p]
[?p :person/status "inactive"])
OR clauses — alternative patterns:
(or [?p :person/city "New York"]
[?p :person/city "Boston"])
(or-join [?p]
[?p :role/admin true]
[?p :role/superuser true])
Vector functions — operate on cardinality-vector attributes:
[(nth ?vec 0) ?first-element] ;; get element by index
[(first ?vec) ?head] ;; first element
[(last ?vec) ?tail] ;; last element
[(length ?vec) ?len] ;; number of elements
[(contains? ?vec "value") ?found] ;; boolean membership test
[(index-of ?vec "value") ?pos] ;; index of first match
[(subvec ?vec 1 3) ?slice] ;; sub-vector [start, end)
enumerate — expands a vector into one tuple per element with index. This is the primary way to query vector contents:
;; Given :product/tags is a vector ["electronics" "sale" "new"]
[:find ?tag ?idx
:where [?e :product/label "Widget"]
[?e :product/tags ?vec]
[(enumerate ?vec) [?idx ?tag]]]
;; Returns: [0 "electronics"], [1 "sale"], [2 "new"]
Important: enumerate produces multiple output tuples from a single input tuple. The binding [?idx ?tag] is required — it destructures each element into an index and value. The data pattern that binds ?vec must appear before the enumerate expression in the where clause, and the planner must not reorder it ahead of the pattern that provides the vector.
Database functions:
[(get-else $ ?e :person/nickname "unknown") ?nick]
[(missing? $ ?e :person/email)]
[(get-some $ ?e :person/nick :person/name :person/email) ?display]
Subqueries — nested queries that bind results into the outer query:
;; Tuple binding — subquery returns one tuple, binds to variables
[(q [:find (max ?h)
:in $ ?sym
:where [?p :price/symbol ?sym]
[?p :price/high ?h]]
$ ?s) [[?max-high]]]
;; Relation binding — subquery returns multiple tuples
[(q [:find ?p ?h
:in $ ?sym
:where [?p :price/symbol ?sym]
[?p :price/high ?h]]
$ ?s) [[?price ?high] ...]]
The subquery (q [...] $ ?var1 ?var2) takes a full query, then lists the inputs to pass (matching the subquery's :in clause). The binding after ) determines how results are captured: [[?x]] for a single tuple, [[?x ?y] ...] for multiple tuples.
Tagged literals — use typed constants directly in patterns and predicates:
[#identity "L85hash..." :person/name ?name] ;; match specific entity
[?e :event/date #inst "2024-06-15T10:30:00Z"] ;; match timestamp
[(> ?d #inst "2024-01-01T00:00:00Z")] ;; compare against timestamp
Input Parameters
;; Scalar input
[:find ?name :in $ ?target-age
:where [?p :person/age ?target-age]
[?p :person/name ?name]]
;; Collection input
[:find ?name :in $ [?city ...]
:where [?p :person/city ?city]
[?p :person/name ?name]]
Pass :in values with the -in flag (one per binding, order matches :in after $):
# Scalar input
~/go/bin/datalog -db <path> \
-query '[:find ?name :in $ ?age :where [?p :person/age ?age] [?p :person/name ?name]]' \
-in 30
# String input (quote in EDN)
~/go/bin/datalog -db <path> \
-query '[:find ?age :in $ ?name :where [?p :person/name ?name] [?p :person/age ?age]]' \
-in '"Alice"'
# Collection input
~/go/bin/datalog -db <path> \
-query '[:find ?name :in $ [?city ...] :where [?p :person/city ?city] [?p :person/name ?name]]' \
-in '["New York" "Boston"]'
# Multiple inputs
~/go/bin/datalog -db <path> \
-query '[:find ?name :in $ ?min ?max :where [?p :person/age ?a] [(>= ?a ?min)] [(<= ?a ?max)] [?p :person/name ?name]]' \
-in 20 -in 30
Each -in value is parsed as EDN, so tagged literals work too: -in '#inst "2024-01-01T00:00:00Z"'.
Order By
[:find ?name ?age
:order-by [?age :desc] [?name :asc]
:where [?p :person/name ?name]
[?p :person/age ?age]]
Time-Travel
// Open a database
d, _ := db.Open("path/to/db")
// History — all raw datoms, no CRDT resolution
hist := d.History()
hist.Query(`[:find ?name ?tx :where [?p :person/name ?name ?tx]]`)
// As-of — CRDT-resolved state at a specific transaction
txID, _ := tx.Commit() // returns datalog.ElementID
asOf := d.AsOf(txID)
asOf.Query(`[:find ?name :where [?p :person/name ?name]]`)
Common Debugging Patterns
1. Explore an unknown database
Start by discovering the schema shape:
# What attributes exist?
~/go/bin/datalog -db <path> -query '[:find ?a :where [_ ?a _]]'
# How many entities per attribute?
~/go/bin/datalog -db <path> -query '[:find ?a (count ?e) :where [?e ?a _]]'
Then sample values for attributes that look interesting:
~/go/bin/datalog -db <path> -query '[:find ?v :where [_ :person/status ?v]]'
2. Inspect an entity through a known value
Don't query for entity IDs and copy them. Instead, join through a known value to see everything about an entity:
# All attributes and values for the entity named "Alice"
~/go/bin/datalog -db <path> -query \
'[:find ?a ?v :where [?e :person/name "Alice"] [?e ?a ?v]]'
This works for any attribute — find the entity through something you know, then fan out:
# Everything about the order with ID "ORD-1234"
~/go/bin/datalog -db <path> -query \
'[:find ?a ?v :where [?e :order/id "ORD-1234"] [?e ?a ?v]]'
3. Follow references
Entities reference other entities. Use variable joins to traverse:
# Who are Alice's friends, and what cities do they live in?
~/go/bin/datalog -db <path> -query \
'[:find ?friend-name ?city
:where [?p :person/name "Alice"]
[?p :person/friend ?f]
[?f :person/name ?friend-name]
[?f :person/city ?city]]'
Chain as many hops as needed — each shared variable (?f above) is a join.
4. Find missing or unexpected data
# People without an email
~/go/bin/datalog -db <path> -query \
'[:find ?name
:where [?p :person/name ?name]
(not [?p :person/email _])]'
# People in neither New York nor Boston
~/go/bin/datalog -db <path> -query \
'[:find ?name ?city
:where [?p :person/name ?name]
[?p :person/city ?city]
[(!= ?city "New York")]
[(!= ?city "Boston")]]'
5. Aggregate to find outliers
# Which cities have the most people?
~/go/bin/datalog -db <path> -query \
'[:find ?city (count ?p)
:where [?p :person/city ?city]]'
# Average age by city
~/go/bin/datalog -db <path> -query \
'[:find ?city (avg ?age)
:where [?p :person/city ?city]
[?p :person/age ?age]]'
6. For small databases, just export
~/go/bin/datalog -db <path> -export dump.edn
Export format is one datom per line: [#identity "hash" :attribute value txid]
Output Format
Results are printed as markdown tables:
| ?name | ?age |
|---------|------|
| Alice | 30 |
| Bob | 25 |
| Charlie | 35 |
_3 tuples (1.234ms)_
With -verbose, stderr gets annotation events showing index selection, join stats, and timing.
Entity Identity
Entities are identified by an Identity — a SHA1 hash of a seed string, displayed as a 25-character L85-encoded string (a sort-order-preserving Base85 encoding).
When you query for an entity variable like ?e, the result symbol shows the L85 hash. You don't normally need to use these directly — join through known attribute values instead (see debugging patterns above).
The #identity "L85hash" tagged literal exists for cases where you have a hash from logs or export output and need to look it up directly. This is the exception, not the normal workflow.
Notes
- All attribute names are keywords starting with
:(e.g.,:person/name) - Integer literals are int64, float literals are float64
- String literals use double quotes:
"value" - The
_wildcard matches any value without binding - Empty results return a table with headers but no tuples
- Tagged literals:
#identity "L85...",#inst "RFC3339...",#bytes "L85..."
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/datalog-query-mm8o51yi/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/datalog-query-mm8o51yi' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/datalog-query-mm8o51yi/download' --output '.skill-downloads/datalog-query-mm8o51yi/SKILL.md.part' && mv '.skill-downloads/datalog-query-mm8o51yi/SKILL.md.part' '.skill-downloads/datalog-query-mm8o51yi/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
Tools declared by the author (check availability in your agent): Bash(~/go/bin/datalog *)
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && test -s '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/datalog' && cp -R '.skill-downloads/datalog-query-mm8o51yi' '.claude/skills/datalog'Cursor
test -f '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && test -s '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/datalog' && cp -R '.skill-downloads/datalog-query-mm8o51yi' '.cursor/skills/datalog'Codex
test -f '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && test -s '.skill-downloads/datalog-query-mm8o51yi/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/datalog' && cp -R '.skill-downloads/datalog-query-mm8o51yi' '.agents/skills/datalog'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/datalog-query-mm8o51yi'openskills read 'datalog-query-mm8o51yi'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/datalog-query-mm8o51yi/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
Create Interactive Maps with kepler.gl
Data & AI
Create interactive map visualizations and export standalone HTML using the keplergl Python package. Use to plot geospatial data from DataFrames, GeoJSON, or CSV.
Agent-Driven Parameter Optimization
Data & AI
Guide for optimizing configurable systems with a measurable objective, using bounded trials and evidence-based recommendations.
ICP Visitor Outreach
Data & AI
Identify signed-up users matching the ideal customer profile but stalled, and draft personalized outreach to re-engage them.