Pure RAM, per-skill, declarative in-memory indexing framework for AI skill authors
Project description
vapor-idx
Pure RAM. Per-skill. Gone when you're done.
vapor-idx is a zero-dependency, fully declarative, in-memory indexing framework
for AI skill authors. It gives individual skills a structured, queryable,
traversable knowledge store that lives entirely in process memory and disappears
the moment the skill ends — no files, no databases, no persistence, no cross-skill
leakage.
It is RAM — shaped, named, and queryable — scoped to exactly one skill invocation at a time.
GitHub: https://github.com/RebornBeat/vapor-idx Discord: https://discord.gg/vapor-idx
Why does this exist?
When you write a Claude skill that needs to reason over structured data — a code analysis skill that must track function relationships, a document skill that must link sections to their references, an image skill that must index pixels spatially, a 3D skill that must traverse mesh topology — you need somewhere to put that structure while the skill runs. Without a tool like this, every skill author re-implements ad-hoc Maps, arrays of objects, and linear scans.
vapor-idx provides the scaffold so skill authors declare their data model once
and query it cleanly, without reinventing indexing primitives every time.
What it is not:
- It does not persist anything. Ever. By design.
- It does not share state between skills.
- It does not use embeddings, ML models, or vector search.
- It does not have defaults. Every type, field, and index strategy is declared explicitly by the skill author.
- It does not enforce opinions about your data shape.
vapor.store(type, data) stores in RAM. Records live as JavaScript objects,
Python dicts, and Rust structs — inside the running process, held in working
memory, gone when the skill ends. Nothing is written to disk unless the skill
author explicitly chooses to serialise a snapshot themselves.
The broader vision: modality-first indexing
vapor-idx is not limited to traditional structured data. Because it indexes any
declared schema into RAM relationships, it can decompose and traverse any
modality that can be expressed as typed records with connections.
Images without ML dependencies
Instead of YOLO, ResNet, or any external model, declare a Pixel type and index
every pixel by position and colour. Traverse adjacency relationships. Claude —
acting as the zero-shot intelligence within the skill — applies its own semantic
reasoning over the structured index to identify regions, edges, and objects.
Reconstruct from the indexed structure back to a .png file, an SVG, or a CSS
layout — no ML dependency, no external model call, pure RAM.
const vapor = createVapor({
types: {
Pixel: {
fields: {
x: { type: 'number', index: 'range' },
y: { type: 'number', index: 'range' },
r: { type: 'number', index: 'range' },
g: { type: 'number', index: 'range' },
b: { type: 'number', index: 'range' },
edgeScore: { type: 'number', index: 'range' },
region: { type: 'string', index: 'exact' },
},
relationships: {
ADJACENT_TO: { targetTypes: ['Pixel'], directed: false, cardinality: 'many-to-many' },
BELONGS_TO: { targetTypes: ['Region'], directed: true, cardinality: 'many-to-one' },
},
},
Region: {
fields: {
label: { type: 'string', index: 'keyword' },
area: { type: 'number', index: 'range' },
centerX: { type: 'number', index: 'range' },
centerY: { type: 'number', index: 'range' },
},
},
},
});
3D mesh without a render engine
Index every vertex, edge, face, and material from an OBJ/STL/GLTF as typed
records with spatial relationships. Traverse mesh topology. Emit a Blender Python
script that reconstructs the full .blend file, or reconstruct a clean OBJ —
from indexed structure alone. No rendering engine required during analysis. The
full Blender API surface can be reconstructed from indexed data, including
armatures, physics, materials, animations, and modifiers — all as pure Python.
const vapor = createVapor({
types: {
Vertex: {
fields: {
x: { type: 'number', index: 'range' },
y: { type: 'number', index: 'range' },
z: { type: 'number', index: 'range' },
normalX: { type: 'number', index: 'none' },
normalY: { type: 'number', index: 'none' },
normalZ: { type: 'number', index: 'none' },
materialIndex: { type: 'number', index: 'exact' },
},
relationships: {
CONNECTED_TO: { targetTypes: ['Vertex'], directed: false, cardinality: 'many-to-many' },
PART_OF: { targetTypes: ['Face'], directed: true, cardinality: 'many-to-many' },
},
},
Face: {
fields: {
materialIndex: { type: 'number', index: 'exact' },
area: { type: 'number', index: 'range' },
smoothGroup: { type: 'number', index: 'exact' },
},
relationships: {},
},
Material: {
fields: {
name: { type: 'string', index: 'exact' },
roughness: { type: 'number', index: 'range' },
metallic: { type: 'number', index: 'range' },
baseColorR:{ type: 'number', index: 'range' },
baseColorG:{ type: 'number', index: 'range' },
baseColorB:{ type: 'number', index: 'range' },
},
relationships: {},
},
},
});
Cross-modal reconstruction
Index elements from one modality, traverse semantically, and reconstruct in another. An indexed SVG design becomes a CSS layout. An indexed audio waveform becomes a MIDI file. An indexed set of pixels becomes a vector graphic. An indexed 3D mesh becomes a Blender Python script. Claude traverses the relationships and drives the reconstruction — the index is the bridge, not any ML model.
This works because Claude is the zero-shot intelligence when a skill runs. vapor-idx provides the structure; Claude provides the reasoning. Together they enable modality understanding and reconstruction without any external model calls.
Monorepo structure
Skills execute in multiple runtimes. A single npm package cannot serve a Python computer-use skill, and a Python package cannot serve a browser artifact. Each package is a faithful implementation of the same API contract in its target language.
vapor-idx/
├── README.md
├── TESTING.md ← deploy + test guide
├── DOCS/
│ ├── CONCEPT.md
│ ├── API_REFERENCE.md
│ ├── PIXEL_INDEXING.md
│ ├── 3D_INDEXING.md
│ └── CROSS_MODAL.md
├── skills/
│ ├── pixel-analyzer/SKILL.md
│ ├── mesh-analyzer/SKILL.md
│ ├── design-indexer/SKILL.md
│ └── cross-modal-reconstructor/SKILL.md
└── packages/
├── vapor-idx/ ← TypeScript / JavaScript (Node.js + Browser ESM)
│ ├── package.json
│ ├── tsconfig.json
│ └── src/
│ ├── index.ts
│ ├── types.ts
│ ├── VaporInstance.ts
│ ├── SchemaValidator.ts
│ ├── RecordStore.ts
│ ├── RelationshipStore.ts
│ ├── QueryEngine.ts
│ ├── TraversalEngine.ts
│ └── indexes/
│ ├── ExactIndex.ts
│ ├── KeywordIndex.ts
│ ├── PrefixIndex.ts
│ └── RangeIndex.ts
│
├── vapor-idx-py/ ← Python 3.10+ (PyPI)
│ ├── pyproject.toml
│ └── vapor_idx/
│ ├── __init__.py
│ ├── types.py
│ ├── instance.py
│ ├── schema.py
│ ├── record_store.py
│ ├── relationship_store.py
│ ├── query_engine.py
│ ├── traversal_engine.py
│ └── indexes/
│ ├── __init__.py
│ ├── exact.py
│ ├── keyword.py
│ ├── prefix.py
│ └── range_.py
│
└── vapor-idx-rs/ ← Rust (crates.io)
├── Cargo.toml
└── src/
├── lib.rs
├── types.rs
├── instance.rs
├── schema.rs
├── record_store.rs
├── relationship_store.rs
├── query_engine.rs
├── traversal_engine.rs
└── indexes/
├── mod.rs
├── exact.rs
├── keyword.rs
├── prefix.rs
└── range.rs
Which runtime should I use?
| Environment | Runtime | Install |
|---|---|---|
| Claude Code / Node.js skill scripts | Node.js | npm install vapor-idx |
| Claude web Artifacts (browser ESM) | Browser | CDN or bundled ESM |
| Claude computer-use (Python) | Python 3.10+ | pip install vapor-idx |
| Rust-based skill tooling | Rust | cargo add vapor-idx |
Installation
Node.js / Browser (TypeScript / JavaScript)
npm install vapor-idx
Browser ESM (no build step)
<script type="module">
import { createVapor } from 'https://esm.sh/vapor-idx';
</script>
Python
pip install vapor-idx
Rust
[dependencies]
vapor-idx = "0.1"
Core concepts
Schema-first, no defaults
You declare every type, every field, and the index strategy for every field before storing a single record. There are no auto-detected types, no implicit indexes, no convenience defaults. The schema is the law of your skill's index.
Index strategies
| Strategy | What it indexes | Supported operations |
|---|---|---|
none |
Nothing — stored only | Retrieval by ID only |
exact |
Exact value equality | eq, neq, in, notIn |
keyword |
Tokenised inverted index | contains, free-text keywords |
prefix |
Trie-based prefix matching | startsWith |
range |
Sorted numeric values | gt, lt, gte, lte |
Range indexes correctly handle negative numbers, zero, and positive numbers across all three language implementations.
Records are RAM variables
vapor.store(type, data) returns a string ID and holds the record in process
memory as a native language value — a JavaScript object, a Python frozen
dataclass, a Rust struct. Nothing touches the filesystem. Records disappear when
the instance is destroyed or the process ends.
Relationships
Types can declare named relationship kinds between them. Relationships are
directional or bidirectional, with declared cardinality (one-to-one,
one-to-many, many-to-many). They are stored as first-class edges traversable
by the query and traversal engines.
Traversal
The traversal engine follows relationship edges from a starting node using BFS, up to a declared depth, with optional filtering applied at each hop. It returns both the matched records and the full path metadata.
Snapshots
At any point you can take a snapshot of the entire index state as a plain serialisable value. Snapshots are RAM-only — serialisation to disk is the caller's responsibility.
Quick start — TypeScript
import { createVapor } from 'vapor-idx';
const vapor = createVapor({
types: {
Function: {
fields: {
name: { type: 'string', index: 'exact' },
filePath: { type: 'string', index: 'exact' },
docstring: { type: 'string', index: 'keyword' },
lineStart: { type: 'number', index: 'range' },
lineEnd: { type: 'number', index: 'range' },
isAsync: { type: 'boolean', index: 'exact' },
tags: { type: 'string[]', index: 'keyword' },
visibility: { type: 'string', index: 'prefix' },
},
relationships: {
CALLS: {
targetTypes: ['Function'],
directed: true,
cardinality: 'many-to-many',
},
DEFINED_IN: {
targetTypes: ['Module'],
directed: true,
cardinality: 'many-to-one',
},
},
},
Module: {
fields: {
path: { type: 'string', index: 'exact' },
language: { type: 'string', index: 'exact' },
summary: { type: 'string', index: 'keyword' },
},
},
},
});
const modId = vapor.store('Module', {
path: 'src/parser.ts',
language: 'typescript',
summary: 'Parses AST nodes and extracts function signatures',
});
const fnId = vapor.store('Function', {
name: 'parseFunction',
filePath: 'src/parser.ts',
docstring: 'Extracts function name, parameters, and return type from an AST node',
lineStart: 42,
lineEnd: 78,
isAsync: false,
tags: ['parsing', 'ast', 'extraction'],
visibility: 'public',
});
vapor.relate(fnId, 'DEFINED_IN', modId);
const asyncFns = vapor.query({
type: 'Function',
where: { field: 'isAsync', op: 'eq', value: true },
});
const parsingFns = vapor.query({
type: 'Function',
keywords: 'ast extraction',
});
const largeFns = vapor.query({
type: 'Function',
where: { field: 'lineEnd', op: 'gt', value: 100 },
orderBy: { field: 'lineStart', direction: 'asc' },
limit: 10,
});
const callChain = vapor.traverse({
from: fnId,
relationship: 'CALLS',
direction: 'outgoing',
depth: 3,
});
console.log(vapor.stats());
const snap = vapor.snapshot();
const fork = vapor.restore(snap);
vapor.destroy();
Quick start — Python
from vapor_idx import create_vapor, QueryOptions, FieldFilter, TraversalOptions, PathOptions
vapor = create_vapor({
"types": {
"Function": {
"fields": {
"name": {"type": "string", "index": "exact"},
"file_path": {"type": "string", "index": "exact"},
"docstring": {"type": "string", "index": "keyword"},
"line_start": {"type": "number", "index": "range"},
"is_async": {"type": "boolean", "index": "exact"},
"tags": {"type": "string[]", "index": "keyword"},
},
"relationships": {
"CALLS": {
"targetTypes": ["Function"],
"directed": True,
"cardinality": "many-to-many",
},
},
},
},
})
fn_id = vapor.store("Function", {
"name": "parse_function",
"file_path": "parser.py",
"docstring": "Extracts function signatures from AST nodes",
"line_start": 42,
"is_async": False,
"tags": ["parsing", "ast"],
})
results = vapor.query(QueryOptions(
type="Function",
keywords="ast extraction",
))
print(vapor.stats())
vapor.destroy()
Quick start — Rust
use vapor_idx::{
create_vapor, VaporSchema, TypeDefinition, FieldDefinition,
FieldType, IndexStrategy, QueryOptions, FieldFilter, FilterOp,
};
use std::collections::HashMap;
fn main() {
// VaporSchema is a plain struct — initialise with struct literal
let schema = VaporSchema {
types: HashMap::from([
("Function".to_string(), TypeDefinition {
fields: HashMap::from([
("name".to_string(), FieldDefinition {
field_type: FieldType::String,
index: IndexStrategy::Exact,
required: true,
}),
("line_start".to_string(), FieldDefinition {
field_type: FieldType::Number,
index: IndexStrategy::Range,
required: true,
}),
]),
relationships: HashMap::new(),
}),
]),
};
// create_vapor returns VaporResult<VaporInstance>
let mut vapor = create_vapor(schema).expect("valid schema");
let fn_id = vapor.store(
"Function",
serde_json::json!({ "name": "parse_function", "line_start": 42 }),
).unwrap();
let results = vapor.query(&QueryOptions {
type_filter: Some(vec!["Function".to_string()]),
where_filters: vec![FieldFilter {
field: "line_start".to_string(),
op: FilterOp::Gt,
value: serde_json::json!(10),
}],
..Default::default()
}).unwrap();
println!("Found {} results", results.total);
vapor.destroy();
}
API reference
createVapor(schema) / create_vapor(schema) → VaporInstance
Creates a new isolated index bound to the provided schema. Validates the schema
at construction time. No types, fields, or relationships exist until declared.
In Rust, returns VaporResult<VaporInstance>.
vapor.store(type, data) → string (record ID)
Stores a record of the given type in RAM as a native language object. Returns
a generated ID (vpr_<timestamp>_<counter>). Throws if the type is not declared
or required fields are missing.
vapor.get(id) → VaporRecord | null
Returns a single record by ID. O(1) lookup from a HashMap/dict/Map.
vapor.update(id, partial) → void
Updates fields on an existing record. Re-indexes all changed fields.
vapor.delete(id) → void
Removes a record and all its relationship edges from the index.
vapor.relate(sourceId, relType, targetId, metadata?) → string (edge ID)
Creates a relationship edge. Validates both records exist, the relationship type is declared, and the target type is permitted. Bidirectional relationships create edges in both directions automatically.
vapor.unrelate(edgeId) → void
Removes a relationship edge by edge ID.
vapor.query(options) → QueryResult
Executes a query against the index.
interface QueryOptions {
type?: string | string[]; // restrict by type
where?: FieldFilter | FieldFilter[]; // field-level predicates
keywords?: string | string[]; // free-text across keyword fields
logic?: 'AND' | 'OR'; // how multiple where clauses combine
limit?: number;
offset?: number;
orderBy?: { field: string; direction: 'asc' | 'desc' };
}
Operators by index strategy:
| Strategy | Valid operators |
|---|---|
exact |
eq, neq, in, notIn |
keyword |
contains |
prefix |
startsWith |
range |
gt, lt, gte, lte |
none |
not directly queryable |
vapor.traverse(options) → TraversalResult
BFS traversal following relationship edges from a starting node.
interface TraversalOptions {
from: string;
relationship: string;
direction?: 'outgoing' | 'incoming' | 'both';
depth?: number;
filter?: Omit<QueryOptions, 'limit' | 'offset' | 'orderBy'>;
}
vapor.findPath(options) → string[] | null
Returns the shortest ID path between two records, or null if no path exists
within maxDepth.
vapor.getRelationships(id, type?, direction?) → VaporRelationship[]
Returns all relationship edges for a given record, optionally filtered.
vapor.stats() → VaporStats
Returns record counts by type, relationship counts by type, index sizes, and a rough memory estimate in bytes.
vapor.snapshot() → VaporSnapshot
Captures the full current state as a plain serialisable object. RAM only.
vapor.restore(snapshot) → VaporInstance
Returns a new VaporInstance hydrated from a snapshot. Schema hashes must match.
vapor.destroy() → void
Releases all references. Safe to call multiple times.
Skill authoring guidelines
Declare only what you need. Memory scales with index type and cardinality.
For spatial/pixel/vertex data, always use range on coordinate fields. Range
queries (gt/lt/gte/lte) are the foundation of spatial understanding. All
three implementations correctly handle negative coordinates and zero.
Let Claude traverse — Claude is the zero-shot intelligence. vapor-idx provides structure; Claude provides semantic reasoning. A skill does not need an external ML model to understand modality relationships — it indexes the data and lets Claude traverse and interpret.
Destroy explicitly. Call vapor.destroy() when done to release memory
promptly rather than waiting for scope exit or GC.
Use snapshots for branching logic. Snapshot before exploring an interpretation; restore the losing branch.
Do not pass instances across skill boundaries. Each skill creates its own instance from its own schema.
Philosophy
vapor-idx is deliberately minimal. It does not implement:
- Embeddings or vector similarity
- ML-guided ranking or confidence scoring
- Persistence to any storage medium
- Cross-skill or cross-session state
- Schema migrations
- Query optimiser hints
- Transactions or rollback
These are intentional exclusions. The moment you need any of them, you have outgrown an in-skill ephemeral index and you need a real database.
What vapor-idx does is provide a clean, consistent, language-agnostic API for declaring structured knowledge in RAM and querying it with zero dependencies. Combined with Claude's own reasoning, this is sufficient to analyse, reconstruct, and transform any modality that can be expressed as typed records with connections.
Package publishing
| Package | Registry | Import |
|---|---|---|
vapor-idx |
npm | import { createVapor } from 'vapor-idx' |
vapor-idx |
PyPI | from vapor_idx import create_vapor |
vapor-idx |
crates.io | use vapor_idx::create_vapor; |
All three packages are versioned in lockstep.
Documentation
- DOCS/CONCEPT.md — core philosophy and the RAM-first indexing model
- DOCS/API_REFERENCE.md — complete type definitions and API
- DOCS/PIXEL_INDEXING.md — image and pixel indexing guide
- DOCS/3D_INDEXING.md — 3D mesh and vertex indexing guide
- DOCS/CROSS_MODAL.md — cross-modal reconstruction guide
- TESTING.md — deploy, test, and validate all three packages with skills
Skills
- skills/pixel-analyzer/SKILL.md
- skills/mesh-analyzer/SKILL.md
- skills/design-indexer/SKILL.md
- skills/cross-modal-reconstructor/SKILL.md
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vapor_idx-0.1.1.tar.gz.
File metadata
- Download URL: vapor_idx-0.1.1.tar.gz
- Upload date:
- Size: 24.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b841439c75c5984f768713cd9f0abc25b1701e5f3676dac0df77746197d0747
|
|
| MD5 |
e53bf4c610b9002908a7af5e074a5bb9
|
|
| BLAKE2b-256 |
ff728cb5cdfc324d67c750e1bfe48a74506f66a5d3ded33766302851b53a1dec
|
File details
Details for the file vapor_idx-0.1.1-py3-none-any.whl.
File metadata
- Download URL: vapor_idx-0.1.1-py3-none-any.whl
- Upload date:
- Size: 28.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06eddfc911152333957fe7a9e7d9fe41604e9db5c93c71319dfe2079eda14512
|
|
| MD5 |
d4ba2d23ba9fd8928669a655031e9e57
|
|
| BLAKE2b-256 |
fe9d52d1b549a9c490559b970ec6a1e59dc38c3f46bc0e3bb8ceca9b7e3b38ba
|