openglacier
Background
Once upon a time, in the land of Numan-ka, there lived a curious creature named Numan-ka. Numan-ka was known for his endless questions and his love for exploring the unknown.
One day, while wandering through the Whispering Woods, Numan-ka met a wise old owl named Enian-ka. Enian-ka had seen many seasons and knew the secrets of transformation.
“Why do you wander so far, young Numan-ka?” asked Enian-ka.
“I seek to become like you,” replied Numan-ka, “to understand the world and grow wise!”
Enian-ka smiled and said, “To become like me, you must first understand yourself. You must learn the ways of patience, curiosity, and change.”
So Numan-ka listened, learned, and grew. Day by day, he practiced the owl’s ways—watching silently, thinking deeply, and adapting to the world around him. Slowly, he began to change—not just in body, but in spirit.
And so, Numan-ka became Enian-ka—not by magic, but by wisdom, effort, and the courage to transform.
Overview of current projects
Two projects are public today, both written in Rust and published under the MIT License. They share one engine: glacierdb is built on openglacier-core and does not fork or duplicate it.
openglacier-core
An event-first, schema-less document database engine and capability runtime written in Rust.
openglacier-core is a database engine. It includes its own query language, planner, execution engine, transactional storage abstraction and native persistent storage backend. It does not sit on top of another database.
It provides three closely related pieces:
og-core: the database kernel and execution engine;ogd: a long-lived daemon exposing database and service capabilities;ogcli: a thin interactive and command-line client.
The native persistent storage engine is called Glacier.
Its first stable release, v1.0, code name antaa palaa, means the core architecture is stable enough to build on. It came after an extended period of real-world use and hostile testing: continuous fuzzing, sustained read/write workloads, failure injection, storage corruption and I/O failure testing, crash recovery, backup and restore validation, and a large integration test suite. It runs on modest and older hardware too, from the Raspberry Pi 3 and 4 to the Odroid XU4 and a Fairphone 2: resources are valuable.
Small core, long life.
Repository: github.com/openglacier/core
glacierdb
An event-first, schema-less document database built on the openglacier engine.
glacierdb, aka openglacier-db, is a database distribution built on openglacier-core: a ready-to-run database daemon and CLI with the Glacier persistent storage backend enabled by default. It is designed to be used as a database first, and provides:
- a schema-less document model;
- a declarative, pipeline-oriented query language;
- transactional storage;
- persistent native storage through Glacier;
- authentication and identity support;
- event subscriptions;
- memory governance;
- backup and restore;
- an interactive command-line client;
- an optional terminal UI.
openglacier-core
│
└── database engine and resource runtime
glacierdb, aka openglacier-db
│
└── database distribution and user-facing binaries
Repository: github.com/openglacier/glacierdb · Downloads and the full README: the glacierdb page
The language
Both speak the same query, modeling and view language, described in its own book, The OpenGlacier Modeling Language.
glacierdb
docker pull openglacier/glacierdbglacierdb is a database distribution built on openglacier Core.
It provides a ready-to-run database daemon and CLI with the Glacier persistent storage backend enabled by default.
Read the book: The OpenGlacier Modeling Language →
glacierdb is designed to be used as a database first.
It provides:
- a schema-less document model;
- a declarative, pipeline-oriented query language;
- transactional storage;
- persistent native storage through Glacier;
- authentication and identity support;
- event subscriptions;
- memory governance;
- backup and restore;
- an interactive command-line client;
- an optional terminal UI.
Underneath, glacierdb uses the same database engine as openglacier.
glacierdb
│
▼
┌─────────────────┐
│ openglacier-core│
│ │
│ query engine │
│ storage │
│ transactions │
│ events │
│ authentication │
└────────┬────────┘
│
▼
Glacier storage
The distinction is intentional:
openglacier-core
│
└── database engine and resource runtime
glacierdb, aka openglacier-db
│
└── database distribution and user-facing binaries
glacierdb does not fork or duplicate the database engine.
It consumes the published openglacier-core crate.
Quick start
Debian and Ubuntu
On Debian 12 (bookworm) or later, Ubuntu 22.04 (jammy) or later and Raspberry Pi OS 64-bit, for amd64 and arm64, glacierdb comes from the openglacier apt repository:
sudo apt install -y curl ca-certificates
curl -fsSLo /tmp/openglacier-archive-keyring.deb https://repo.openglacier.org/openglacier-archive-keyring.deb
sudo apt install /tmp/openglacier-archive-keyring.deb
sudo apt update
sudo apt install glacierdb
openglacier-archive-keyring installs the signing key of the repository (/usr/share/keyrings/openglacier-archive-keyring.gpg) and its apt source (/etc/apt/sources.list.d/openglacier.sources), and keeps the key up to date. The key’s fingerprint is 9E6F B980 0022 3F0F 299B FE23 DC7F 6E62 4B08 5E78: gpg --show-keys /usr/share/keyrings/openglacier-archive-keyring.gpg shows it.
glacierdb runs at once as the glacierdb.service systemd service, enabled at boot:
address: 127.0.0.1:7878
storage: glacier, /var/lib/glacierdb/data.glacier
configuration: /etc/glacierdb/glacierdb.env (then systemctl restart glacierdb)
bootstrap: admin password in /etc/glacierdb/bootstrap-password
GlacierCLI comes with it: run glaciercli to connect.
sudo apt install glacierdb-full switches to the full profile (native import data worker). On amd64 the package holds an x86-64 build and an x86-64-v3 one, the latter used on CPUs with AVX2. Updates come with apt upgrade.
Docker
docker pull openglacier/glacierdb
Then start glacierdb using the configuration appropriate for your deployment.
From source
Clone the repository and start the daemon:
cargo run --release --bin glacierdb
glacierdb uses Glacier storage by default.
By default:
address: 127.0.0.1:7878
storage: glacier
authorization: permissive
capabilities: auth,database,events
The storage backend can still be explicitly selected.
For example, to use in-memory storage:
OGD_STORAGE=memory \
cargo run --release --bin glacierdb
The OGD_* environment variable interface is intentionally compatible with openglacier.
Connect with GlacierCLI
In another terminal:
cargo run --release --bin glaciercli
You should get:
Connected to 127.0.0.1:7878. Type help for commands.
glacierdb>
The CLI is intentionally thin.
It speaks the same protocol as the underlying openglacier daemon and does not introduce a separate database API.
Your first documents
Insert some documents:
from products
| insert {
_id: "keyboard",
name: "Keyboard",
category: "hardware",
price: 129
}
from products
| insert {
_id: "mouse",
name: "Mouse",
category: "hardware",
price: 49
}
from products
| insert {
_id: "desk",
name: "Desk",
category: "furniture",
price: 399
}
Query them:
from products
| where category == "hardware"
| sort price desc
| select name, price
Or execute a query directly:
glaciercli \
'from products | where price > 100 | sort price desc'
No schema declaration.
No SQL server.
No external database.
What is glacierdb?
glacierdb is a database distribution around the openglacier database engine.
The engine itself lives in:
openglacier-core
and provides the fundamental database primitives:
query language
│
▼
lexer / parser
│
▼
AST
│
▼
logical planning
│
▼
physical planning
│
▼
execution
│
▼
storage abstraction
│
├── Memory
│
└── Glacier
glacierdb packages those capabilities into a database-oriented runtime.
Its main binaries are:
glacierdb database daemon
glaciercli interactive and command-line client
The underlying Core API remains available independently for applications that need to embed or build directly on the engine.
Why glacierdb?
glacierdb is intended to make openglacier immediately recognizable as a database product without changing the underlying engine architecture.
It keeps the database engine:
- headless;
- portable;
- storage-independent;
- resource-conscious;
- query-oriented;
- independently usable as a Rust crate.
At the same time, glacierdb provides a conventional database distribution:
glacierdb
│
├── glacierdb
│
├── glaciercli
│
├── Glacier persistent storage
│
├── authentication
│
└── events
Query language
glacierdb uses a declarative, pipeline-oriented query language.
This section is a summary. The complete guide and reference, with one entry per stage and per function, views, caches, modeling patterns, the grammar and an index, is the book The OpenGlacier Modeling Language (og-books/manifest/the_ogml_language).
A query names a source, then describes a sequence of transformations separated by |:
from orders
| where status == "paid"
| derive net_total = total - discount
| sort net_total desc
| limit 20
The language is intentionally:
- schema-less;
- declarative;
- pipeline-oriented;
- not SQL;
- not MongoDB syntax;
- not a scripting language.
Each stage has one responsibility.
Execution order is explicit. Execution strategy is not: the planner decides how a pipeline actually runs.
This section is the complete reference:
- A short tour
- Query structure
- Lexical structure
- Values and types
- Expressions
- Functions
- Parameters, variables and
explain - Stage reference
- Past states and history
- Views
- Composition rules
- System collections
- Results and statistics
- Errors
- Known limitations
- Grammar summary
The examples use three collections:
users { name, age, city, active, tags: [...], profile: { country, score } }
orders { order_id, user, product, qty, price, status, month, region }
products { code, label, category, embedding: [x, y, z] }
| users | age | city | active | tags | profile.country | profile.score |
|---|---|---|---|---|---|---|
| Alice | 31 | Paris | true | admin, dev | FR | 9.5 |
| Bob | 17 | Lyon | false | dev | FR | 7 |
| Carol | 45 | Berlin | true | (empty) | DE | 8.25 |
| Dan | 28 | Paris | true | ops | FR | 6 |
| orders | user | product | qty | price | status | month | region |
|---|---|---|---|---|---|---|---|
| 1 | Alice | P1 | 2 | 10.0 | paid | 2026-01 | EU |
| 2 | Alice | P2 | 1 | 99.5 | paid | 2026-02 | EU |
| 3 | Bob | P1 | 5 | 10.0 | pending | 2026-01 | EU |
| 4 | Carol | P3 | 1 | 250.0 | paid | 2026-02 | US |
| 5 | Dan | P2 | 3 | 99.5 | cancelled | 2026-01 | US |
| 6 | Carol | P1 | 4 | 10.0 | paid | 2026-01 | US |
| products | label | category | embedding |
|---|---|---|---|
| P1 | Pen | office | [1.0, 0.0, 0.0] |
| P2 | Printer | office | [0.9, 0.1, 0.0] |
| P3 | Phone | mobile | [0.0, 1.0, 0.0] |
To keep outputs short, the _id field of each result document is omitted.
In the examples, a line starting with -- shows a result or a remark. It is not part of the query: the language has no comments.
A short tour
Filter, compute, rank. This is a Top-N over a computed value:
on orders
| where status != "cancelled"
| select user, product, price * qty as total
| sort total desc
| limit 3
{"product":"P3","total":250.0,"user":"Carol"}
{"product":"P2","total":99.5,"user":"Alice"}
{"product":"P1","total":50.0,"user":"Bob"}
Revenue per region, with a derived value feeding a grouped sum:
on orders
| where status == "paid"
| derive revenue = price * qty
| group region, revenue as revenue
| sort revenue desc
{"region":"US","revenue":290.0}
{"region":"EU","revenue":119.5}
Several measures at once, with the compound group:
on orders | group | by region | sum qty as units | avg price as mean | count as rows | end
Expressions are one language everywhere. Arithmetic, membership and functions work in filters, projections and updates alike:
on orders
| where price * qty > 50 and status in ["paid", "pending"]
| select order_id, round(price * qty, 1) as total, if(qty > 2, "bulk", "unit") as kind
Vector similarity search, by cosine distance:
on products
| near embedding, [0.1, 0.9, 0.0]
| sort _distance
| limit 2
| select code, label, _distance
Correlated joins. Each user receives their own large orders:
on users as u
| where age > 30
| lookup orders as o
| where o.user == u.name and o.price * o.qty > 20
| select order_id, price
| into big_orders
| end
Running values over ordered rows:
on orders
| window
| partition by region
| order by order_id
| cumsum qty as running_units
| row_number as rank
| end
Transactional writes. set acts on exactly the documents that reach it, even after a sort and a limit:
on users | sort name | limit 1 | set top = true
on orders | where status == "cancelled" | delete
on users | where active | into archive
Engine introspection is queryable like any other collection:
on _memory | where scope == "global" | select profile, current_bytes, pressure_state
Query structure
[explain] [let ... |] <source> ( "|" <stage> )*
Source
on <collection> [as of <date|generation> | history] [as <alias>]
from <collection> [as of <date|generation> | history] [as <alias>]
on values [ {...}, ... ]
on view <name> [version <n>] [as <alias>]
onandfromare synonyms.- The collection name may be dotted:
on shop.orders. - Collections are created implicitly by the first write. Reading a collection that does not exist returns nothing.
- A query may consist of the source alone:
on usersreturns every document. - The alias names the source. At the top level,
u.ageandageare the same field (on users as u | where u.age > 30). Inlookupandunionsub-pipelines, the alias names the outer document. on values [...]reads literal rows, objects with bare or quoted keys. It works wherever a collection is read, includinglookupandunion. The rows can never be written.as ofandhistoryare described in Past states and history,on viewin Views.
on values [{code: "FR", rate: 0.2}, {code: "DE", rate: 0.19}] | where rate > 0.19
-- {"code":"FR","rate":0.2}
Simple stages
| <stage-name> <arguments>
Everything up to the next top-level | is the argument text of the stage. Parentheses, brackets and braces must balance. A | inside (...), [...], {...} or a string does not end the stage.
Compound stages and end
Some stages open a sub-pipeline, a sequence of stages closed by | end:
| Stages | Rule |
|---|---|
union, pivot, window | Always compound |
lookup / join | Compound, except the one-line form whose header contains into |
load, group | Compound when written without arguments; compact one-line form otherwise |
| every other stage | Always simple |
on users
| lookup orders
| where status == "paid"
| into paid
| end
| where active
- A missing
| endis an error. - An
endwithout an open sub-pipeline is an error. - Sub-pipelines can nest.
Lexical structure
Whitespace
Spaces, tabs and newlines are interchangeable. A query can sit on one line or span many, and indentation is cosmetic.
There are no comments in the language.
The pipe
| separates stages and is the only statement separator. || is always the boolean “or”, never two stage boundaries.
Identifiers
- An identifier starts with a letter (any Unicode alphabetic character) or
_, and continues with letters, digits or_. - Identifiers are case-sensitive.
- Unicode identifiers work everywhere:
on données | where prénom == "Élodie". - Field paths use dots:
profile.country,a.b.c. Each segment is an identifier. - A key containing a space or a hyphen can be stored (
insert {"display name": "Zed"}) but cannot be written as a path. Read it withget(field, "display name"). - A
?after a path segment makes the path optional:nick?,profile?.country.
Keywords
These words are reserved:
on from as where set lookup join union load pivot
into with chunk replace update merge end
and or not true false null
Other stage names (select, sort, group, limit, …) are ordinary identifiers recognised in stage position.
Inside expressions, in is also reserved. between is recognised only after an operand, so it remains usable as a field name. Function names are not reserved: round(x) is a call and round alone is a field.
Literals
| Literal | Examples | Notes |
|---|---|---|
| Null | null | |
| Boolean | true, false | |
| Integer | 0, 42, 18446744073709551615 | Signed 64-bit when it fits, otherwise unsigned 64-bit |
| Float | 9.5, .75, 1e3, 1.5e-10 | Finite IEEE-754 doubles. 1 and 1.0 compare equal |
| String | "Paris", "a\"b", "line\nbreak" | Double quotes only. JSON escapes: \" \\ \/ \b \f \n \r \t and \uXXXX. No raw newline |
| Array | [1, 2], ["a", qty * 2, lower(name)] | Elements are any expressions |
| Object | {"a": 1}, {name: "Alice", tags: ["x"]} | Quoted keys anywhere in an expression; insert and on values also accept bare keys |
A leading + or - directly applied to a numeric literal is part of the literal: -5 and +2.5 are constants. Between two operands they are binary operators. Applied to a field (-qty), - is a negation.
Operators and punctuation
| Token | Meaning |
|---|---|
== != < <= > >= | Comparisons |
and, or, not | Boolean operators |
&&, ||, ! | Symbolic synonyms of and, or, not |
+ - * / % | Arithmetic |
in, not in | Membership in an array (literal or field) |
between ... and ..., not between ... and ... | Inclusive range test |
? | Optional field path |
name(...) | Function call |
( ) | Grouping |
[ ] | Array constructor |
{ } : , | Objects, argument lists |
. | Field-path and collection-path separator |
= | Assignment in set, derive and let (never comparison) |
$name | Parameter or variable |
Values and types
The value model
Documents are schema-less. Every field holds one of:
| Kind | Description |
|---|---|
null | Explicit null |
| boolean | true / false |
| number | Signed integer (i64), unsigned integer (u64) or finite float (f64) |
| string | UTF-8 text |
| array | Ordered values of any kind, including mixed kinds |
| object | Nested document |
There is no date type. A timestamp is a number of milliseconds since the Unix epoch, or an ISO-8601 string, and the date functions read both.
_id
Every stored document has an _id, a UUID v7.
insertandloadgenerate it when absent.loadaccepts an explicit_id(a 32-hex-digit UUID, with or without dashes). This gives idempotent imports and upserts.where _id == "<uuid>"runs as a primary-key lookup instead of a scan.selectalways keeps_id.
Comparison rules
- Integers and floats compare numerically:
age == 31.0matchesage: 31. - Strings compare lexicographically:
name >= "B" and name < "D". - No cross-kind coercion.
age == "31"is an error, notfalse. Membership is the exception:x in [...]never fails on kinds, it just does not match. - Field-to-field comparisons are allowed:
where age >= profile.score.
Missing fields are strict
A field referenced in a predicate or an expression must exist on every document that reaches it. Otherwise the query fails:
expression evaluation failed: field path "nickname" is missing
This is deliberate: a typo in a field name surfaces as an error instead of silently returning nothing.
Missing is not null, and the language gives explicit tools for optional fields:
exists(nickname)istrueorfalseand never fails.coalesce(nickname, name)returns its first present, non-null argument.and,or,ifandcaseevaluate lazily, soexists(x) and x > 3is safe.- A
?after a path segment reads a missing field asnull:nick?,profile?.country.==and!=acceptnullon either side.
on users | select name, coalesce(nick, name) as display, exists(nick) as has_nick
on users | where profile?.level == "gold" | select name, nick? as nick
Exceptions:
neardiscards documents that lack the vector field.- Projection stages (
select field,drop,rename) tolerate missing fields.
Expressions
There is a single expression language. Anything that works in where works in select ... as, derive, set, sort and inside sub-pipelines, with the same results and the same errors.
Forms
| Form | Example |
|---|---|
| Literal | 42, "paid", true, null |
| Field path | age, profile.country |
| Optional path | nick?, profile?.country |
| Unary | not active, !active, -qty, +qty |
| Binary | age >= 18, price * qty |
| Group | (city == "Paris" or city == "Lyon") |
| Membership | status in ["paid", "pending"], "dev" in tags, region not in ["US"] |
| Range | age between 18 and 30, price not between 10 and 20 |
| Array | [qty, qty * 2, "x"] |
| Function call | round(price * qty, 2), coalesce(nick, name) |
| Object | {"a": 1}, merge(meta, {"v": 2}) |
| Parameter | $min, $cities |
Every form nests inside every other: upper(concat(name, "@", lower(city))), max([a, b, c]) in allowed.
A bare boolean field or literal is a valid predicate: where active, where true.
Precedence
From lowest to highest. Binary operators are left-associative.
| Level | Operators |
|---|---|
| 1 | or, || |
| 2 | and, && |
| 3 (prefix) | not, ! |
| 4 | == != |
| 5 | < <= > >= in not in between ... and ... |
| 6 | + - |
| 7 | * / % |
| 8 (prefix) | unary -, unary + |
not binds looser than comparisons: not city == "Berlin" means not (city == "Berlin"), and not active and age > 18 means (not active) and age > 18.
Semantics
| Construct | Semantics |
|---|---|
| Comparisons | Numbers compare across integer and float; strings lexicographically; any other cross-kind comparison is an error |
and / or / not | Operands must be booleans. and and or short-circuit |
in / not in | The right side must be an array. Incompatible kinds simply do not match |
+ - * % | Integers stay integers while the result fits in 64 bits. Anything involving a float gives a float |
/ | Always a float: 7 / 2 is 3.5 |
| Division by zero | Error. Use div(a, b, fallback) for a safe division |
Unary - | Integer negation when possible, otherwise float |
| Text | No + on strings: use concat(...) |
on orders | where price * qty > 50 and status != "cancelled" | select order_id, round(price * qty, 1) as total
-- {"order_id":2,"total":99.5}
-- {"order_id":4,"total":250.0}
on users | where abs(age - 30) <= 2 and not (name in ["Bob"]) | select name, age
-- {"age":31,"name":"Alice"}
-- {"age":28,"name":"Dan"}
Functions
Functions are called by name with parenthesised arguments, and any expression can be an argument.
Unknown names and wrong argument counts are rejected when the query is planned, before any storage access.
There is no general null propagation: numeric, text and date functions refuse null (`upper` expects a string, found Null). coalesce, exists, optional, nullif, concat, to_int, to_float, to_string, type, merge and the aggregates sum, avg, median, percentile, stddev accept it. Guard optional values with coalesce(x?, default).
Numeric
| Function | Result | Example |
|---|---|---|
abs(x) | Absolute value, integer kept | abs(-3) → 3 |
sqrt(x) | Square root (float); a negative input is an error | sqrt(16) → 4.0 |
pow(x, y) | Power; integer when exact | pow(3, 2) → 9 |
round(x [, digits [, mode]]) | Rounds half away from zero ("half_up", the default) or to even ("half_even"). Integer without digits, float with digits (0 to 15) | round(13.333, 2) → 13.33, round(0.125, 2, "half_even") → 0.12 |
trunc(x [, digits]) | Rounds toward zero, same rules as round | trunc(-2.7) → -2 |
floor(x), ceil(x) | Rounded down / up, as an integer | floor(12.5) → 12 |
sign(x) | -1, 0 or 1 | sign(-3) → -1 |
ln(x), log10(x), log(x, base), exp(x) | Logarithms (positive input) and exponential, as floats | log(8, 2) → 3.0 |
min(a, b, ...), max(a, b, ...) | Smallest / largest argument; with one array argument, over its elements; values must be comparable (a null item is an error) | max(age, 30), max(scores) |
sum(a, b, ...), avg(a, b, ...) | Sum (integer while every term is) / mean; with one array argument, over its elements; null items are skipped | sum(scores), avg([]) → null |
median(a, b, ...), percentile(array, fraction), stddev(a, b, ...) | Middle value, value at a fraction (0 to 1, linear between the two closest values), sample standard deviation; with one array argument, over its elements; null items are skipped; null when there are no values (fewer than two for stddev) | median([3, 1, 2]) → 2.0, percentile(scores, 0.9) |
div(a, b [, fallback]) | Safe division: fallback when b is 0 (null without fallback), else a / b | div(price, qty - 1, 0) |
Logic, missing values and conversions
| Function | Result | Example |
|---|---|---|
if(cond, then, else) | Only the chosen branch is evaluated | if(age >= 18, "adult", "minor") |
case(c1, v1, c2, v2, ..., [default]) | Value of the first true condition, else default (or null); lazy | case(qty > 5, "bulk", qty > 1, "few", "one") |
coalesce(a, b, ...) | First argument that is present and not null | coalesce(nick, name) |
exists(x) | Whether x is present; never fails | exists(nickname) |
optional(x) | x, or null when missing; what x? means | optional(nick) |
nullif(a, b) | null when a == b, else a | nullif(status, "") |
between(x, low, high) | low <= x <= high; what x between low and high means | between(age, 18, 30) |
bucket(x, [b0, b1, ...] [, default]) | Lower bound of the range [b(i), b(i+1)) holding x, else default (or null); the last bound closes the last range | bucket(31, [0, 18, 65]) → 18, bucket(70, [0, 18, 65], "old") → "old" |
to_int(x) | Integer from a number (truncated), a numeric string or a boolean; null stays null | to_int("42") → 42 |
to_float(x) | Float from a number, a numeric string or a boolean | to_float("2.5") → 2.5 |
to_string(x) | Text; numbers, booleans, arrays and objects are written as JSON | to_string(3) → "3" |
to_bool(x) | From a boolean, a number (non-zero), or true/false/1/0/yes/no | to_bool("yes") → true |
type(x) | "null", "bool", "number", "string", "array" or "object" | type(profile) → "object" |
Text
Positions count characters, not bytes. A negative start counts from the end.
| Function | Result | Example |
|---|---|---|
len(x) | Characters of a string, items of an array, keys of an object | len(tags) → 2 |
lower(s), upper(s), trim(s), ltrim(s), rtrim(s) | Text transforms | upper(trim(name)) |
concat(a, b, ...) | Joins text (numbers and booleans are written as text, null is skipped); when every argument is an array, concatenates the arrays | concat(name, "@", city), concat(tags, ["new"]) |
substr(s, start [, length]) | Substring | substr("Paris", 0, 3) → "Par" |
split(s, separator) | Array of parts; an empty separator splits characters | split("a,b", ",") → ["a","b"] |
replace(s, from, to) | Replaces every occurrence | replace(city, " ", "-") |
index_of(x, y) | Position of y in a string or an array, -1 when absent | index_of(tags, "dev") → 1 |
pad_left(s, width [, fill]), pad_right(...) | Pads to width characters with fill (one character, a space by default) | pad_left("3", 3, "0") → "003" |
contains(x, y) | Substring test on strings, element test on arrays | contains(name, "li"), contains(tags, "dev") |
starts_with(s, p), ends_with(s, p) | Prefix / suffix test | starts_with(city, "Pa") |
Arrays
| Function | Result | Example |
|---|---|---|
at(array, i) | Item i (negative from the end), null when out of range | at(tags, -1) → "dev" |
slice(array, start [, length]) | Sub-array | slice(tags, 1) |
reverse(x) | Reversed array or string | reverse(tags) |
sort_array(array [, "asc"|"desc"]) | Sorted copy; items must be comparable | sort_array(scores, "desc") |
unique(array) | Distinct items, first occurrence kept | unique([1, 1, 2]) → [1,2] |
flatten(array) | Flattens one level | flatten([[1], 2]) → [1,2] |
range(start, end [, step]) | Integers from start up to (excluding) end; at most 1,000,000 items | range(0, 6, 2) → [0,2,4] |
Objects
| Function | Result | Example |
|---|---|---|
keys(o), values(o) | Keys / values of an object | keys(profile) → ["country","score"] |
get(o, key [, default]) | Value under any key, including keys that are not identifiers | get(meta, "display name", "") |
has(o, key) | Whether the key is present | has(profile, "score") |
merge(o1, o2, ...) | Shallow merge, later objects win; null arguments are skipped | merge(profile, {"level": 2}) |
Dates
A timestamp is a number of milliseconds since the Unix epoch, or an ISO-8601 string (2026-03-09, 2026-03, 2026-03-09T10:00:00Z, 2026-03-09 10:00:00+02:00).
Every computation is in UTC.
Units are year, quarter, month, week (weeks start on Monday), day, hour, minute, second, millisecond, singular or plural.
| Function | Result | Example |
|---|---|---|
now() | Current time, epoch milliseconds | |
parse_date(t) | Epoch milliseconds | parse_date("1970-01-02") → 86400000 |
date_trunc(t, unit) | Start of the unit, epoch milliseconds | date_trunc(at, "month") |
date_add(t, n, unit) | Epoch milliseconds; adding months clamps the day | date_add("2026-01-31", 1, "month") → 2026-02-28 |
date_diff(start, end, unit) | Unit boundaries crossed from start to end | date_diff("2025-12-31", "2026-01-01", "year") → 1 |
date_part(t, part) | year, quarter, month, week (ISO), day, day_of_week (1 = Monday), day_of_year, hour, minute, second, millisecond | date_part(at, "day_of_week") |
date_format(t [, pattern]) | Text; ISO-8601 by default. Directives %Y %m %d %H %M %S %L (ms) %j %u %V %F %T %% | date_format(at, "%Y-%m") → "2026-03" |
fiscal_year(t, closing) | Fiscal year holding t, named after the year it closes in. closing is the closing month (1 to 12; 12 is the calendar year), or a timestamp on the first day of the opening month | fiscal_year("2026-07-01", 6) → 2027 |
fiscal_month(t, closing) | Month within the fiscal year, 1 to 12 | fiscal_month("2026-07-01", 6) → 1 |
fiscal_start(t, closing), fiscal_end(t, closing) | First and last day of the fiscal year, epoch milliseconds | fiscal_end("2026-07-01", 6) → 2027-06-30 |
month_end(t) | Last day of the month, epoch milliseconds | month_end("2024-02-10") → 2024-02-29 |
days_in(t, unit) | Number of days of the unit holding t | days_in("2024-02-10", "month") → 29 |
Parameters, variables and explain
Parameters ($name)
A query can reference named parameters. Their values travel next to the query in the query.execute request:
{"query": "on users | where age >= $min and city in $cities | limit $n",
"params": {"min": 18, "cities": ["Paris", "Lyon"], "n": 10}}
With glaciercli:
glaciercli --param min=18 --param 'cities=["Paris","Lyon"]' --param n=10 \
'on users | where age >= $min and city in $cities | limit $n'
- Each
$nameoutside a string literal is replaced by the literal of its value before the query is authorized, planned or run. Strings are quoted and escaped, so a value never changes the structure of the query. - Any JSON value works: numbers, strings, booleans,
null, arrays (forin), objects (forinsert). A parameter can appear wherever its literal could:limit $n,near embedding, $vector. - An unbound
$namefails the request. Unused parameters are ignored. - With
glaciercli, the value is parsed as JSON when it can be, otherwise taken as a string.
Variables (let)
A query can start with one or more let headers that name values reused by its stages:
let <$name> = <expression>, <$name> = <expression>, ...
| [let ... |]
on <collection> | ...
let $threshold = 50, $label = upper("big")
| on orders
| where price * qty > $threshold
| derive margin = price * qty - $threshold, kind = $label
| select order_id, margin, kind
{"kind":"BIG","margin":49.5,"order_id":2}
{"kind":"BIG","margin":200.0,"order_id":4}
{"kind":"BIG","margin":248.5,"order_id":5}
- Each expression is evaluated once per query, before planning, then used wherever
$nameappears.let $now = now()therefore gives every row the same instant. - An expression may use any function, request parameters and variables declared before it. It cannot read document fields:
let $x = qtyis rejected. - A name is bound once. Redeclaring a variable, or declaring a name also passed as a parameter, is an error.
let $name ?= <expression>gives a default: it binds the name only when it is not bound already.- A value written as a query in parentheses is the array of its rows, read once before the query is planned. The inner query must not write and returns at most 1,000 rows:
let $best = (on users | sort profile.score desc | limit 1)
| let $name = get(at($best, 0), "name")
| on orders
| where user == $name
| select order_id, user
-- {"order_id":1,"user":"Alice"}
-- {"order_id":2,"user":"Alice"}
explain
Prefixing a query with explain plans it without running it, and returns one document describing the plan:
explain on orders | where qty > 1 | sort price * qty desc | limit 3
| Field | Meaning |
|---|---|
access | collection_scan or primary_key_lookup |
executor | streaming, governed_blocking, transactional (writes), or rejected when the shape has no bounded executor |
mode | Memory contract of the plan |
operators | Physical operators in order, with shorthand forms expanded |
read_only | Whether the query writes |
Variables and parameters are bound first, so explain let $n = 2 | on orders | limit $n shows limit 2.
Stage reference
| Role | Stages |
|---|---|
| Filtering and projection | where, select / project, derive, rename, drop, root |
| Vector search | near |
| Ordering and paging | sort, limit, skip / offset, first, single, sample |
| Arrays | unwind |
| Deduplication and counting | distinct, count |
| Aggregation | group, bucket, pivot |
| Ordered rows | window, fill |
| Combining collections | lookup / join, union |
| Writes | insert, set, delete, into / out, load |
A running daemon lists every stage with its forms, arguments and placement:
.query.stages
where
where <predicate>
Keeps the documents for which the predicate is true. Several where stages may be chained.
on users | where age >= 18 and city == "Paris" | select name, age
-- {"age":31,"name":"Alice"}
-- {"age":28,"name":"Dan"}
on users | where (city == "Paris" or city == "Lyon") and age < 30 | select name
on users | where profile.score >= 8 | select name, profile.score
on users | where age >= 18 | where active | select name
select
select <item>, <item>, ...
item := <field.path> | <expression> as <field.path>
project is an alias.
Keeps only the listed fields, plus _id. Nested sources are rebuilt nested: select profile.score returns {"profile":{"score":...}}. An alias can be a nested path. A computed item needs as.
on orders | select order_id, price * qty as total, product as sku | limit 2
-- {"order_id":1,"sku":"P1","total":20.0}
-- {"order_id":2,"sku":"P2","total":99.5}
on users | select name, profile.country as geo.country | limit 1
-- {"geo":{"country":"FR"},"name":"Alice"}
Items are evaluated left to right, and a later item can use an alias introduced earlier: select price * qty as total, total > 50 as big.
derive
derive <field.path> = <expression>, <field.path> = <expression>, ...
Adds or overwrites fields while keeping the rest of the document. Target paths may be nested. Assignments are applied left to right, and each one sees the fields computed before it:
on orders
| derive total = price * qty, big = total > 100, label = if(big, "big", "small")
| select order_id, total, label
| limit 2
-- {"label":"small","order_id":1,"total":20.0}
-- {"label":"small","order_id":2,"total":99.5}
derive does not modify stored data.
rename
rename <source.path> as <target.path>
Moves one field. Source and target must differ.
on users | rename name as full_name | select full_name, age | limit 1
-- {"age":31,"full_name":"Alice"}
drop
drop <field.path>, <field.path>, ...
Removes fields from the output. It does not modify stored data.
on users | drop tags, profile | limit 1
-- {"active":true,"age":31,"city":"Paris","name":"Alice"}
root
root <field.path>
Replaces each document with the object stored at the path.
on users | where name == "Alice" | root profile
-- {"country":"FR","score":9.5}
near
near <vector.field>, [<number>, <number>, ...]
Computes the cosine distance between the query vector and the vector field of each document, and writes it to _distance (0 means same direction).
- Documents without the field are discarded.
- A dimension mismatch is an error.
neardoes not reorder documents. Pair it withsort _distanceandlimit kfor k-nearest-neighbours, or withwhere _distance < tfor a radius search.
on products | near embedding, [0.0, 1.0, 0.0] | sort _distance | limit 2
on products | near embedding, [0.0, 1.0, 0.0] | where _distance < 0.5 | select code, _distance
-- {"_distance":0.0,"code":"P3"}
on products | where category == "office" | near embedding, [0.0, 1.0, 0.0] | select code, _distance
sort
sort <key> [asc|desc], ...
key := <field.path> | <expression>
Stable ordering on one or more keys. A key can be any expression: sort price * qty desc, sort len(name), name.
- The default direction is
asc. - Missing fields and
nullsort before every other value in ascending order, and after them in descending order. - Followed by
limit N,sortruns as a bounded Top-N that keeps only N rows in memory. - A large sort spills sorted runs to disk instead of growing in memory.
on orders | sort price desc | limit 2
on orders | sort price * qty desc | limit 3
on users | select name, city | sort city, name desc
limit, skip
limit <non-negative integer>
skip <non-negative integer>
offset is an alias of skip.
on orders | sort order_id | skip 2 | limit 2 | select order_id
-- {"order_id":3}
-- {"order_id":4}
first, single
first [<field.path>]
single [<field.path>]
first returns the first document that reaches it. single returns the only document that reaches it, and fails when there are zero or several. With a path, both also project that field.
on orders | select order_id, price | sort price desc | first
-- {"order_id":4,"price":250.0}
on users | where name == "Alice" | single | select name, age
-- {"age":31,"name":"Alice"}
on users | where city == "Paris" | single
-- error: single expected exactly 1 row(s), found more
first stops the scan as soon as one row has passed, and sort ... | first runs as a bounded Top-1.
sample
sample <n>
Returns n documents chosen uniformly at random, or all of them when there are fewer. Memory is bounded by n whatever the input size.
on orders | where status == "paid" | sample 2
unwind
unwind <field.path> [with index <field.path>] [keep empty]
Emits one row per item of the array at the path, with the item in place of the array, in order.
- A row whose field is missing or not an array passes through unchanged.
- A row whose array is empty is dropped, unless
keep emptyis given: the field is thennull. with index <field>stores the 0-based position of each item.
on users | unwind tags with index position | select name, tags, position
-- {"name":"Alice","position":0,"tags":"admin"}
-- {"name":"Alice","position":1,"tags":"dev"}
-- {"name":"Bob","position":0,"tags":"dev"}
-- {"name":"Dan","position":0,"tags":"ops"}
on users | unwind tags keep empty | where tags == null | select name
-- {"name":"Carol"}
distinct
distinct -- whole-document equality
distinct <field.path>, ... -- keeps the first document per distinct key
on orders | select status | distinct
-- {"status":"paid"}
-- {"status":"pending"}
-- {"status":"cancelled"}
on orders | distinct region, status
count
count
count as <alias>
Terminal. It returns a single document holding the number of input documents.
on orders | where price > 20 | count as expensive_orders
-- {"expensive_orders":3}
group
Compact form:
group <key> [as <alias>] -- group + count
group <key> [as <alias>], ..., <measure> [as <alias>] -- group + sum(measure)
- One item: group by that key and emit
count. - Two or more items: every item except the last is a grouping key, and the last item is summed.
- Every key and the measure can be renamed with
as. Keys may be nested paths.
on orders | group status
-- {"count":4,"status":"paid"}
-- {"count":1,"status":"pending"}
-- {"count":1,"status":"cancelled"}
on orders | group region, qty
-- {"qty":8,"region":"EU"}
-- {"qty":8,"region":"US"}
on orders | group region as zone, month, qty as units | sort units desc
on orders | group product | sort count desc | limit 2
-- {"count":3,"product":"P1"}
-- {"count":2,"product":"P2"}
Compound form, for several measures:
group
| by <key> [as <alias>], ...
| sum|avg|min|max|first|last|list|median|stddev <value> [where <predicate>] [as <alias>]
| percentile <value> <fraction> [where <predicate>] [as <alias>]
| first|last <value> by <order> [where <predicate>] [as <alias>]
| count [<value>] [where <predicate>] [as <alias>]
| ...
| end
by is required and appears exactly once. Measures appear zero or more times, in any order. With no measure, the group emits count.
| Measure | Result | Default alias |
|---|---|---|
sum <field> | Sum of the numeric values (integer while exact) | <field> |
avg <field> | Mean of the numeric values, null when there are none | avg_<field> |
min <field>, max <field> | Smallest / largest non-null value | min_<field>, max_<field> |
first <field>, last <field> | Value on the first / last row of the group, in input order | first_<field>, last_<field> |
first <value> by <order>, last <value> by <order> | Value on the row of smallest / largest <order>, whatever the input order | first_<field>, last_<field> |
count | Number of rows | count |
count <field> | Number of rows where the field is present and not null | count_<field> |
list <field> | Array of the values neither missing nor null, in input order | list_<field> |
median <value>, percentile <value> <fraction>, stddev <value> | The functions of the same name over the group’s numbers | median_<field>, percentile_<field>, stddev_<field> |
- A measured value, and a
by <order>, may be any expression. An expression needsas <alias>. where <predicate>measures only the rows the predicate keeps.count where <predicate>counts the kept rows.- Grouping keys are field paths. To group by a computed value,
deriveit first. - A sum stays an integer while every term is an integer and the total fits in 64 bits. Non-numeric values are ignored by sums.
- Groups are emitted in group-key order. A large
groupspills to disk instead of growing in memory.
on orders | group | by region | avg price as mean | max qty | count as rows | end
-- {"max_qty":5,"mean":39.833333333333336,"region":"EU","rows":3}
-- {"max_qty":4,"mean":119.83333333333333,"region":"US","rows":3}
on orders
| group
| by region
| sum price * qty as revenue
| sum qty where status == "paid" as paid_units
| count where status == "cancelled" as cancelled
| last product by order_id as last_product
| list user as customers
| end
-- {"cancelled":0,"customers":["Alice","Alice","Bob"],"last_product":"P1","paid_units":3,"region":"EU","revenue":169.5}
-- {"cancelled":1,"customers":["Carol","Dan","Carol"],"last_product":"P1","paid_units":5,"region":"US","revenue":588.5}
bucket
bucket <expression> by [<b0>, <b1>, ...] [default <value>] [as <alias>]
Counts rows per range [b(i), b(i+1)). Each row falls in the bucket named by its lower bound. Rows outside every range go to default, or to null without it. The alias defaults to bucket. Boundaries must be strictly ascending.
on users | bucket age by [0, 18, 40] default "40+" as band
-- {"band":0,"count":1}
-- {"band":18,"count":2}
-- {"band":"40+","count":1}
bucket is shorthand for derive <alias> = bucket(<expression>, [...], <default>) | group <alias>.
pivot
pivot
| rows <field>, ...
| columns <field>, ...
| values <field> [as <alias>] | <expression> as <alias>, ...
| aggregate sum|count|avg|min|max|first|last
| end
Emits one row per row key, with one cell per column value. Each directive appears exactly once.
valuestakes one or more measures, written likeselectitems. With one measure, each cell is named after its column. With several, cells are named<column>.<name>.- A cell with no row is absent: read it with
?. - Stages may follow
pivot. - The aggregate cells must fit the query memory budget:
pivotdoes not spill.
on orders | pivot | rows region | columns month | values qty | aggregate sum | end
-- {"2026-01":7,"2026-02":1,"region":"EU"}
-- {"2026-01":7,"2026-02":1,"region":"US"}
on orders | pivot | rows region | columns month | values qty, price * qty as rev | aggregate sum | end
-- {"2026-01.qty":7,"2026-01.rev":70.0,"2026-02.qty":1,"2026-02.rev":99.5,"region":"EU"}
-- {"2026-01.qty":7,"2026-01.rev":338.5,"2026-02.qty":1,"2026-02.rev":250.0,"region":"US"}
window
window
| partition by <field>, ...
| order by <field> [asc|desc], ...
| lag|lead <field> [<rows>] [as <alias>]
| cumsum|first|last <field> [as <alias>]
| row_number [as <alias>]
| ...
| end
Adds to every row values computed over its partition, taken in order by order.
order byis required and appears once.partition byis optional: without it, the whole input is one partition. At least one function is required.- Rows come out sorted by partition, then order, and keep all their fields.
- Functions run in order, so one may read a field an earlier one added.
| Function | Value on each row | Default alias |
|---|---|---|
lag <field> [<rows>] | The field <rows> rows earlier in the partition (default 1); null before the first | lag_<field> |
lead <field> [<rows>] | The field <rows> rows later; null after the last | lead_<field> |
cumsum <field> | Running sum up to this row; null before the first number | cumsum_<field> |
first <field>, last <field> | The field on the first / last row of the partition | first_<field>, last_<field> |
row_number | 1, 2, 3… within the partition | row_number |
on orders
| window
| partition by region
| order by order_id
| lag qty as previous
| cumsum qty as running
| row_number as n
| end
| select region, order_id, qty, previous, running, n
{"n":1,"order_id":1,"previous":null,"qty":2,"region":"EU","running":2}
{"n":2,"order_id":2,"previous":2,"qty":1,"region":"EU","running":3}
{"n":3,"order_id":3,"previous":1,"qty":5,"region":"EU","running":8}
{"n":1,"order_id":4,"previous":null,"qty":1,"region":"US","running":1}
{"n":2,"order_id":5,"previous":1,"qty":3,"region":"US","running":4}
{"n":3,"order_id":6,"previous":3,"qty":4,"region":"US","running":8}
window works at the top level of a pipeline. The sort it performs spills to disk when needed, and each partition must fit the query memory budget.
fill
fill <field> from <start> to <end> step <step> [by <field>, ...] [with <field> = <value>, ...]
Adds, in each partition, a row for every value of the grid start, start + step, … up to end that no row of the partition holds in <field>.
- The partition is the rows whose
byfields compare equal, or the whole input withoutby. - A new row holds the
byfields of the partition, the grid value and thewithvalues. Every other field is missing. - Rows already there pass unchanged. Rows come out sorted by the
byfields, then<field>. - The grid steps by a positive number (
from 0 to 100 step 10), or by a date unit for date bounds (step "month"), written asstartis:"2026-01"gives"2026-02","2026-03"… - A grid holds at most 100,000 values. Bounds may be parameters.
on orders
| group | by region, month | sum qty as units | end
| fill month from "2026-01" to "2026-03" step "month" by region with units = 0
{"month":"2026-01","region":"EU","units":7}
{"month":"2026-02","region":"EU","units":1}
{"month":"2026-03","region":"EU","units":0}
{"month":"2026-01","region":"US","units":7}
{"month":"2026-02","region":"US","units":1}
{"month":"2026-03","region":"US","units":0}
fill pairs naturally with window: fill the missing months, then read lag <field> 12 for the same month of the previous year.
lookup
lookup <collection> [as <alias>]
| <read-only stages>
| into <field>
| end
join is an alias.
For each input document, lookup runs the sub-pipeline over <collection> and stores the resulting documents as an array in <field>.
into <field>is mandatory and appears exactly once.- Field scope. Inside the sub-pipeline,
<lookup alias>.fieldreads the joined document,<outer alias>.fieldreads the document being enriched, and unqualified fields read the joined document. - Any expression can mix both documents: comparisons, arithmetic,
in, functions. - The sub-pipeline may hold
where,select,derive,skip,limit,first,unwind, and blocking stages (sort,group,distinct), which run on the rows joined to each outer row. - Lookups can nest, and stages may follow the
end.
on users as u
| lookup orders as o
| where o.user == u.name
| select order_id, status
| into orders
| end
| select name, orders
{"name":"Alice","orders":[{"order_id":1,"status":"paid"},{"order_id":2,"status":"paid"}]}
{"name":"Bob","orders":[{"order_id":3,"status":"pending"}]}
{"name":"Carol","orders":[{"order_id":4,"status":"paid"},{"order_id":6,"status":"paid"}]}
{"name":"Dan","orders":[{"order_id":5,"status":"cancelled"}]}
Attach only the most expensive order of each user:
on users as u
| lookup orders as o
| where o.user == u.name
| sort price desc
| first
| select order_id, price
| into top_order
| end
One-line equality form. When the join is a single equality, the sub-pipeline can be omitted:
lookup <collection> [as <alias>] on <joined field> == <outer field> into <field>
on orders | lookup products on code == product into product
on users as u | lookup orders as o on o.user == u.name into orders
The left side reads the joined collection and the right side the document being enriched. Rows where either field is missing do not match.
Execution. When the first where of the sub-pipeline begins with joined == outer, or with starts_with(outer, joined), the joined collection is indexed once on the joined field instead of being scanned for each outer row. Several leading equalities make one composite key. The prefix form gives longest-prefix matching:
on orders as o
| lookup values [{prefix: "P", family: "other"}, {prefix: "P1", family: "pens"}] as r
| where starts_with(o.product, r.prefix)
| derive length = len(prefix) | sort length desc | first
| select family
| into family
| end
| select order_id, product, family
| limit 3
-- {"family":[{"family":"pens"}],"order_id":1,"product":"P1"}
-- {"family":[{"family":"other"}],"order_id":2,"product":"P2"}
-- {"family":[{"family":"pens"}],"order_id":3,"product":"P1"}
lookup values [...] as v joins literal rows, and lookup view <name> as v joins a view.
union
union
| on <collection> [as <alias>]
| <read-only stages>
| end
Emits the main stream, then the documents of the sub-pipeline. The first sub-stage must be on <collection>.
on users | select name | union | on products | select label | end
A branch takes any read pipeline (sort, group, lookup, another union), and any stage may follow the end:
on orders
| where region == "EU"
| group | by month | sum qty as units | end
| union
| on orders
| where region == "US"
| group | by month | sum qty as units | end
| end
| group | by month | sum units as units | end
-- {"month":"2026-01","units":14}
-- {"month":"2026-02","units":2}
Writes
Every write runs in one transaction: all mutations commit, or none do.
Write stages are terminal: nothing can follow them.
The response contains the mutation counters (inserted, replaced, deleted). insert of one document and set also return the written documents.
insert
insert { <key>: <value>, ... }
insert [ { <key>: <value>, ... }, ... ]
Inserts one document, or every object of an array in one transaction. Keys may be unquoted identifiers or JSON strings. insert must be the only stage of its pipeline.
With an array, a row carrying the _id of an existing document is merged into it.
on users | insert {name: "Frank", age: 52, tags: ["new"], profile: {country: "IT"}, active: true}
on users | insert [{name: "Gus", age: 20}, {name: "Hal", age: 33}]
set
set <field.path> = <expression>, ...
Updates every document that reaches it. Target paths can be nested and are created when missing.
Every value is computed from the document as it was before the set, so set a = b, b = a swaps two fields.
on users | where name == "Bob" | set active = true, profile.level = "gold", age = 18
on users | where name == "Bob" | set age = age + 1, nickname = lower(name)
on users | sort name | limit 1 | set top = true
sort, skip and limit can precede set.
delete
delete
Deletes every document that reaches it. It takes no arguments.
on orders | where status == "cancelled" | delete
on staging | delete
into
into <collection>
out is an alias.
Inserts every row that reaches it into another collection, in one transaction, and returns no rows. Each stored row gets a fresh _id. The source collection is never modified.
The target must differ from the source and cannot be a system collection.
on users | where active | into archive
on orders | group | by region | sum qty as units | end | into region_totals
load
Bulk import and upsert.
load
| with replace | update | merge
| chunk [ {...}, {...}, ... ]
| chunk [ ... ]
| end
withsets the mode and is required exactly once. At least onechunkis required.- Each chunk is a JSON array of objects.
loadmust be the first stage.- Rows without an
_idget a fresh one. Rows with an_idare matched against existing documents:
| Mode | _id exists | _id absent from the collection | Row without _id |
|---|---|---|---|
replace | whole document replaced | inserted | inserted |
merge | incoming fields merged over stored ones | inserted | inserted |
update | incoming fields merged over stored ones | error, nothing committed | error |
The whole load is atomic. An invalid _id, a duplicate _id in one request or an update miss rolls back every chunk.
on tmp | load | with merge | chunk [{"_id":"01900000-0000-7000-8000-0000000000a1","a":1,"b":1}] | end
on tmp | load | with merge | chunk [{"_id":"01900000-0000-7000-8000-0000000000a1","b":2,"c":3}] | end
on tmp
-- {"_id":"01900000-0000-7000-8000-0000000000a1","a":1,"b":2,"c":3}
on tmp | load | with merge | chunk [{"n":1},{"n":2}] | chunk [{"n":3}] | end
Past states and history
A source can be read at a past point, or as the list of its stored versions.
History must be kept by the store:
OGD_HISTORY=on
Without it, as of and history fail with this store does not keep history.
on orders as of "2026-09-01T12:00:00Z" | where status == "paid" | count
on orders as of 1842 | sort price desc | limit 10
on orders history | where _op == "delete"
on orders as of "2026-09-01" | into orders_restored
as of <string>reads the store as it was at that time (ISO 8601; a date alone means midnight UTC).as of <number>reads it at that commit generation. Every collection of the query is read at that point,lookupandunioncollections included.historyreturns one row per stored write of the collection, in commit order. Each row holds the document of the version plus the fields below.- Both are read-only. A stage that writes is rejected, except a terminal
into, which copies the rows into another collection: a restore. - A generation after the current one is an error. A date after the last commit reads the current state.
| Field | Meaning |
|---|---|
_id | Identifier of the history row: distinct and ordered |
_doc_id | Identifier of the document |
_version | Version of the document written, or deleted for a delete row |
_op | insert, replace, delete (the row holds the document as it was deleted), or baseline (present when history recording started) |
_generation | Commit generation |
_at | Commit time (ISO 8601), null when not recorded |
_by, _by_device | Identity and device that made the commit, null when unknown |
Views
A view is a named read query, kept in the _views collection with one document per version.
What a view brings:
- Reuse. A query is written once, named, and read like a collection by any other query.
- Parameters. The same view serves different readers: each one binds its own
$namevalues. - Composition. Views read other views, and can be filtered, joined, grouped or united like any source.
- No hidden cost. A view is expanded before planning, so a query over a view is optimized as if it had been written in full.
- Versions. Every definition is kept. A previous version can be read or restored.
- Kept results. A materialized view computes its rows once and serves them until its sources change.
Like explain, define view is a statement, not a stage:
define view sales.by_region
| description "Units and revenue per region"
| exec
| on orders
| where status == $status
| group | by region | sum qty as units | sum price * qty as revenue | end
| end
| end
execis the last clause. The body runs fromexecto the two finalend.- The parameters of a view are the
$nameits body reads without declaring them withlet. They are bound by the reading query. - The body is checked when the view is defined, and refused when it writes.
- Each
definewrites a new version and supersedes the previous one. History is never rewritten. define view <name> | description "..." | endchanges the description.define view <name> | version <n> | endrestores versionnas a new version.undefine view <name>deletes the active version.explain define view ...checks without writing.
A view is read as a source, in the main pipeline, in a union branch, or as the joined side of a lookup:
let $status = "paid" | on view sales.by_region | sort revenue desc
-- {"region":"US","revenue":290.0,"units":5}
-- {"region":"EU","revenue":119.5,"units":3}
let $status = "paid" | on view sales.by_region version 1 | where region == "EU"
on _views | where name == "sales.by_region" | select version, state, parameters
- The query of the view replaces the source before planning, so the stages that follow are optimized with it: a filter written after a view reaches its source.
- A view can read other views, 8 deep at most. A cycle is refused when the view is defined.
Materialized views. | materialized | before exec keeps the rows of the view. The first read computes them, and later reads use the kept rows until a collection the view reads changes, or a view it reads (at any depth) is redefined, restored or undefined. A materialized view takes no parameter: its body binds what it needs with let, and filters written after on view apply to the kept rows.
- The rows are kept per scope (Place and App instance) in cache collections listed by
on _cache. A write in one Place leaves the rows of the other Places alone. - When a view is redefined,
glacierdbcomputes again, in the background, the materialized views that read it, in every scope they were already kept for: the next reader finds them ready. - The rows of an earlier version of a view are dropped once the new ones are in.
- Separately, the rows of repeated read requests are kept in memory for a minute (the result cache, bounded by the memory profile,
OGD_RESULT_CACHE=offto disable).
define view sales.totals
| materialized
| exec
| on orders | group | by region | sum qty as units | end
| end
| end
Composition rules
Planner rules
| Rule |
|---|
Nothing may follow a terminal stage: set, delete, insert, load, into, count |
insert must be the only stage |
load must be the first stage |
| Unknown stage names are rejected |
| Stage arguments are validated before any storage access |
Filters move towards the source. The planner splits where a and b and moves each filter before the stages that leave alone the fields it reads and the set of rows: derive, select, rename, drop, sort, and group or pivot when the filter reads only their keys. Results and errors are unchanged. explain shows the resulting plan.
Bounded execution
glacierdb never falls back to “load everything into memory”. A read query runs in one of two shapes:
- Pure streaming. Any mix of
where,select,derive,rename,drop,root,near,unwind,skip,limit,first,single, optionally followed bycount. Documents flow one by one, and a row bound (limit,first) stops the scan early. - Streaming prefix, then blocking stages. The blocking stages are
sort,distinct,group,pivot,window,fill,sample,lookupandunion. Any number of them may follow one another, each followed by row-local stages.
In the second shape:
- a
skip/limit(orfirst) after a blocking stage bounds that stage:sort k | skip 10 | limit 5runs as a bounded Top-15; sortandgroupspill sorted runs to disk when their state outgrows the memory they can get;- every blocking stage reserves memory as its state grows, and all of them together stay within the query budget.
A shape with no bounded executor is rejected before execution.
System collections
Collections whose name starts with _ are reserved.
They can be queried with the full language. The first four below are virtual: they are computed on read from live engine state.
| Collection | Content |
|---|---|
_memory | One global row (profile, limits, current and peak bytes, pressure state, spill counters) and one row per memory class |
_query_memory | Query admission budget and one row per running operation |
_memory_events | Recent memory reservations and releases |
_index_observations | Per-query-shape execution statistics: fingerprint, access path, executions, scanned, returned, elapsed |
_views | Views and their versions |
_cache | Kept rows of materialized views |
on _memory | where scope == "global" | select profile, current_bytes, pressure_state
on _index_observations | sort executions desc | limit 10
Other system collections (_identities, _devices, _permissions, …) are managed by the daemon and protected by authorization.
Results and statistics
A query returns a stream of documents followed by execution statistics:
{"documents":[...],
"statistics":{"committed":false,"deleted":0,"filtered":1,"inserted":0,
"replaced":0,"returned":3,"scanned":4,
"strategies":["collection_scan"],"streamed":true,
"timings_us":{"parse":29,"plan":116,"execute":436,"materialize_response":55}}}
| Field | Meaning |
|---|---|
scanned / filtered / returned | Documents read, rejected by predicates, emitted |
inserted / replaced / deleted / committed | Write counters, and whether a transaction was committed |
strategies | Physical strategies used: collection_scan, primary_key_lookup, … |
streamed | true when rows were streamed to the client as they were produced |
timings_us | Per-phase timings in microseconds. Plans are cached, so a repeated query skips planning |
With glaciercli, -q prints only the documents and --compact prints the response as compact JSON.
Errors
Errors come from four layers, and each message carries a byte span or a stage location.
| Layer | When | Example |
|---|---|---|
| Lexer | Unknown character, bad string or number | lex error: unexpected character `@` at 9..10 |
| Parser | Pipeline structure | parse error: expected stage name, found operator at 11..12 |
| Planner | Stage arguments, expressions, ordering | unknown stage "frobnicate", unknown function `nope` |
| Execution | Data-dependent | field path "nickname" is missing, single expected exactly 1 row(s), found 0 |
Planning errors are raised before any storage access. Write errors roll back the whole transaction.
Known limitations
| Area | Limitation | Workaround |
|---|---|---|
| Missing fields | A missing field fails the query, except in exists, coalesce and optional paths | x?, exists(x) and ..., coalesce(x, default) |
group keys | Field paths only | derive the computed key first |
sort | The values of a key other than null must all be numbers, all texts or all booleans | to_string(...) or to_float(...) in a derive first |
pivot | Aggregate cells must fit the query memory budget (no spill) | Filter or select before it |
window | Top level only; each partition must fit the query memory budget; keys are field paths | Partition more finely; derive computed keys first |
fill | Top level only; <field> and by are field paths; at most 100,000 grid values | derive computed fields first |
| Dates | No native type; date functions read epoch milliseconds or ISO-8601 strings, in UTC | date_* functions |
| Vector search | Exact distance computation, no approximate index | Filter candidates with where before near |
| Not in the language | Recursive queries, user-defined functions, full-text search, geospatial operators |
Grammar summary
The pipeline layer is below. Each stage then interprets its argument text with the grammar given in the stage reference.
request = [ "explain" ] , { let-header } , query ;
let-header = "let" , variable , ( "=" | "?=" ) , expression , { "," , variable , ( "=" | "?=" ) , expression } , "|" ;
variable = "$" , identifier ;
query = source , { "|" , stage } ;
source = ( "on" | "from" ) , collection , [ temporal ] , [ "as" , identifier ] ;
temporal = "as" , "of" , ( string | number ) | "history" ;
collection = identifier , { "." , identifier } ;
stage = simple-stage | compound-stage ;
simple-stage = stage-name , arguments ;
compound-stage
= ( "lookup" | "join" ) , arguments , sub-pipeline (* unless the header contains `into` *)
| ( "union" | "pivot" | "window" ) , sub-pipeline
| ( "load" | "group" ) , sub-pipeline ; (* header-less form only *)
sub-pipeline = { "|" , stage } , "|" , "end" ;
arguments = { token } ; (* up to the next top-level "|", brackets balanced *)
expression = or-expr ;
or-expr = and-expr , { ( "or" | "||" ) , and-expr } ;
and-expr = not-expr , { ( "and" | "&&" ) , not-expr } ;
not-expr = ( "not" | "!" ) , eq-expr | eq-expr ;
eq-expr = rel-expr , { ( "==" | "!=" ) , rel-expr } ;
rel-expr = add-expr , { ( "<" | "<=" | ">" | ">=" | "in" | "not" , "in" ) , add-expr
| [ "not" ] , "between" , add-expr , ( "and" | "&&" ) , add-expr } ;
add-expr = mul-expr , { ( "+" | "-" ) , mul-expr } ;
mul-expr = unary , { ( "*" | "/" | "%" ) , unary } ;
unary = ( "-" | "+" ) , unary | primary ;
primary = literal | variable | call | field-path | array | object | "(" , expression , ")" ;
object = "{" , [ member , { "," , member } ] , "}" ;
member = string , ":" , expression ;
call = identifier , "(" , [ expression , { "," , expression } ] , ")" ;
array = "[" , [ expression , { "," , expression } ] , "]" ;
field-path = identifier , [ "?" ] , { "." , identifier , [ "?" ] } ;
literal = "null" | "true" | "false" | number | string ;
identifier = ( letter | "_" ) , { letter | digit | "_" } ; (* Unicode letters *)
number = digit , { digit } , [ "." , digit , { digit } ] , [ ( "e" | "E" ) , [ "+" | "-" ] , digit , { digit } ]
| "." , digit , { digit } ;
string = '"' , { char | escape } , '"' ;
escape = "\" , ( '"' | "\" | "/" | "b" | "f" | "n" | "r" | "t" | "u" , 4 * hex ) ;
Document model
Documents are schema-less values.
A document can contain:
strings
numbers
booleans
arrays
objects
null
Dates and times are stored as epoch milliseconds or ISO-8601 strings, and handled by the date functions of the query language.
For example:
{
"_id": "user-42",
"name": "Alice",
"active": true,
"tags": ["rust", "database"],
"profile": {
"country": "FR"
}
}
There is no requirement to declare a schema before inserting documents.
Glacier storage
Glacier is the native persistent storage backend used by glacierdb by default.
It is an append-oriented, record-backed storage engine designed around predictable resource usage and recoverable state.
Conceptually:
+--------------------+--------------------+--------------------+-----+
| Superblock | Segment frame | Segment frame | ... |
+--------------------+--------------------+--------------------+-----+
Writes append new segment data rather than modifying previously committed records in place.
Glacier also supports:
- transactional writes;
- generation/version tracking;
- crash recovery;
- checkpoints;
- primary indexes;
- projected field access;
- memory-mapped reads on supported targets;
- bounded execution integration.
The storage engine is exposed through the Core storage abstraction, allowing the query engine to remain independent of the physical backend.
Persistent database
A persistent local glacierdb instance can be started with:
OGD_STORAGE=glacier \
OGD_STORAGE_PATH=./data/glacierdb.glacier \
cargo run --release --bin glacierdb
The same environment variables used by openglacier remain supported.
For example:
OGD_MEMORY_LIMIT=2GiB
can be used to configure the process memory budget.
Storage backend selection
glacierdb defaults to:
OGD_STORAGE=glacier
when the variable is not explicitly provided.
This does not remove the ability to use another supported backend.
For development or testing:
OGD_STORAGE=memory \
cargo run --release --bin glacierdb
The user-provided environment variable always takes precedence over the glacierdb default.
Authentication
glacierdb includes the openglacier authentication and identity system.
Authorization enforcement can be enabled with:
OGD_AUTH_REQUIRED=true
Node identity configuration uses the same environment variables as openglacier:
OGD_NODE_IDENTITY
OGD_NODE_IDENTITY_FILE
OGD_NODE_IDENTITY_PASSWORD
Authentication is a runtime capability and remains separate from the database storage engine itself.
Events
glacierdb includes the openglacier event engine.
The events capability provides the event delivery layer used by long-lived clients and services.
The event system includes mechanisms for:
- subscriptions;
- heartbeat;
- event delivery;
- retry;
- deduplication;
- local subscribers;
- event outbox processing.
Events are identified by stable event IDs so that retries can be handled without treating the same event as a new event.
Capabilities
glacierdb enables the following service capabilities in its standard distribution:
auth
database
events
The capability model separates service availability from the underlying database engine.
For example:
database
│
├── queries
├── collections
├── storage
└── backups
auth
│
├── identities
├── devices
└── authentication
events
│
├── subscriptions
└── event delivery
The exact operations exposed by a running daemon can be inspected through:
.core.operations
GlacierCLI
glaciercli is the command-line interface for glacierdb.
Start the interactive shell:
glaciercli
The CLI can also execute queries directly:
glaciercli \
'from products | where price > 100 | sort price desc'
Raw operations can be issued using the . prefix:
.ping
.core.health
.core.operations
JSON payloads can be supplied directly:
.collections.list {"stats":true}
Reconnect to the daemon with:
reconnect
or:
.reconnect
Exit with:
exit
quit
or:
.exit
.quit
Terminal UI
The glacierdb distribution includes the cli-tui feature.
The TUI is built on the same CLI and daemon protocol rather than introducing a second execution path.
Build with:
cargo build --release --features cli-tui
The underlying database behavior remains identical.
Configuration
glacierdb preserves the OGD_* configuration interface used by openglacier.
Important variables include:
| Variable | Purpose |
|---|---|
OGD_BIND | Main daemon bind address |
OGD_LOCAL_BIND | Optional local bind address |
OGD_STORAGE | memory or glacier |
OGD_STORAGE_PATH | Glacier store path |
OGD_MEMORY_LIMIT | Process memory limit |
OGD_READ_TIMEOUT_MS | Read timeout |
OGD_WRITE_TIMEOUT_MS | Write timeout |
OGD_NODE_CAPABILITIES | Enabled service capabilities |
OGD_NODE_ROLE | master or node |
OGD_NODE_IDENTITY | Node identity |
OGD_NODE_IDENTITY_FILE | Node identity file |
OGD_NODE_IDENTITY_PASSWORD | Password for the node identity |
OGD_BACKUP_PATH | Backup directory |
OGD_AUTH_REQUIRED | Enable authorization enforcement |
OGD_CLASSIC_AUTH_ENABLED | Enable classic authentication |
OGD_HEARTBEAT_ENABLED | Enable heartbeat |
OGD_HEARTBEAT_INTERVAL_MS | Heartbeat interval |
OGD_DEBUG_QUERY | Query debugging |
OGD_IMPORT_METRICS | Import worker metrics |
Typical glacierdb defaults:
OGD_BIND 127.0.0.1:7878
OGD_STORAGE glacier
OGD_STORAGE_PATH data/ogd.glacier
OGD_NODE_CAPABILITIES auth,database,events
OGD_NODE_ROLE master
Environment variables explicitly supplied by the user take precedence over glacierdb defaults.
Backup and restore
glacierdb exposes database backup operations through the daemon:
backup.create
backup.inspect
backup.restore
They can be accessed through glaciercli using the corresponding operation commands.
For example:
.backup.create {}
The exact operation contracts exposed by the running version can be inspected with:
.core.operations
Memory governance
glacierdb inherits the openglacier memory governance system.
A process-wide memory limit can be configured with:
OGD_MEMORY_LIMIT=1GiB
The execution engine can:
- reserve memory explicitly;
- operate within bounded buffers;
- reclaim eligible cached state;
- spill intermediate execution state when necessary.
The objective is predictable resource usage rather than assuming unlimited RAM.
glacierdb is designed to remain useful on both modern servers and modest hardware.
Projected execution
The Glacier storage engine supports projected field access for eligible query plans.
Conceptually:
Glacier record
│
▼
read required fields
│
▼
query execution
│
▼
identify result documents
│
▼
hydrate complete documents when required
For queries that only require a subset of fields, this can avoid unnecessary decoding and allocation.
For example:
from memories
| near embedding, [1.0, 0.0]
| sort _distance asc
| limit 5
can use only the fields required for candidate evaluation before loading complete result documents where necessary.
Projected execution is an optimization and does not change query semantics.
Vector search
glacierdb supports vector proximity through the near query stage.
from memories
| near embedding, [1.0, 0.0]
| sort _distance asc
| limit 5
The operation computes cosine distance and exposes the result through:
_distance
Smaller values represent closer vectors.
The current implementation performs exact distance computation over candidate documents.
It is not an approximate nearest-neighbour index such as HNSW.
Transactions
Database writes are transactional.
A transaction accumulates mutations and commits them as a storage generation.
Glacier tracks generation and version information so readers can resolve the visible version of a document without modifying previously committed records.
This transactional model is shared through the Core storage contracts rather than being specific to the command-line client.
Protocol
glacierdb uses the openglacier operation and transport model.
Operations have canonical definitions covering information such as:
- wire name;
- operation kind;
- authorization policy;
- execution mode;
- handler domain;
- payload type.
Examples include:
ping
core.health
core.operations
node.status
query.execute
collections.list
storage.stats
events.subscribe
The operation catalogue is discoverable from a running server:
.core.operations
glacierdb does not introduce a separate database protocol.
This makes the glacierdb daemon compatible with the same underlying operation model as openglacier.
Repository structure
The glacierdb repository is intentionally small.
Conceptually:
glacierdb/
│
├── src/
│ └── bin/
│ ├── glacierdb.rs
│ └── glaciercli.rs
│
├── Cargo.toml
├── Cargo.lock
└── README.md
The database engine itself is not copied into this repository.
glacierdb depends on:
openglacier-core
from crates.io.
This keeps the distribution layer separate from the engine implementation.
openglacier Core
glacierdb is built on openglacier Core.
Core provides the reusable database engine and resource runtime:
query engine
storage abstraction
Glacier storage
memory storage
transactions
memory governance
event engine
authentication
identity
operation model
protocol
The Core crate is published independently as:
openglacier-core
The important architectural relationship is:
crates.io
│
▼
┌────────────────────┐
│ openglacier-core │
│ │
│ database engine │
│ storage │
│ events │
│ auth │
└─────────┬──────────┘
│
┌─────────┴─────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ openglacier │ │ glacierdb │
│ │ │ │
│ ogd │ │ glacierdb │
│ ogcli │ │ glaciercli │
└──────────────┘ └──────────────┘
openglacier and glacierdb therefore share the same engine rather than maintaining separate database implementations.
glacierdb versus openglacier
The two distributions serve different purposes.
| glacierdb | openglacier | |
|---|---|---|
| Primary positioning | Database | Resource / database engine ecosystem |
| Database engine | openglacier-core | openglacier-core |
| Daemon | glacierdb | ogd |
| CLI | glaciercli | ogcli |
| Default storage | Glacier | Memory |
| Authentication | Enabled | Enabled |
| Events | Enabled | Enabled |
| TUI | Included | Available through Core features |
| Environment variables | OGD_* | OGD_* |
| Core engine | Same | Same |
The difference is primarily the distribution and default runtime profile.
glacierdb does not replace openglacier Core.
It packages the same engine as a database-oriented product.
Migrating from / to glacierdb to / from core is 100% transparent. You can just switch by keeping your configuration env OGD_*.
Build
Debug build:
cargo build
Release build:
cargo build --release
With the terminal UI:
cargo build --release --features cli-tui
Tests
Run the complete test suite:
cargo test
The glacierdb distribution relies on the same underlying engine and protocol contracts as openglacier Core.
Core-level tests remain in the Core repository.
Development
For local development against a checked-out version of openglacier-core, Cargo can use a local crates.io patch when coordinated development requires it.
The normal glacierdb dependency remains the published crate:
[dependencies]
og_core = {
package = "openglacier-core",
version = "1.0.0",
default-features = false,
features = ["database", "auth", "events", "cli-tui"],
}
The dependency is deliberately aliased as og_core because that is the Rust library name exposed by openglacier-core.
Design principles
Database first
glacierdb presents the engine as a database.
The query language, document model, transactions and storage contracts are first-class components.
Same engine, different distribution
glacierdb does not fork the database implementation.
The database engine remains in openglacier-core.
Glacier by default
glacierdb is intended for persistent database workloads.
Therefore the distribution defaults to the Glacier storage backend while retaining explicit backend selection through OGD_STORAGE.
Compatible configuration
Existing openglacier configuration uses the OGD_* namespace.
glacierdb deliberately preserves that interface.
Headless
The database daemon has no graphical dependency.
It can run:
- on a workstation;
- on a server;
- in a container;
- on small hardware;
- under automated tests.
Resource conscious
Memory and storage behavior should remain explicit and predictable.
glacierdb does not assume unlimited CPU, memory or storage bandwidth.
Small distribution layer
The glacierdb repository contains the distribution layer rather than duplicating the database engine.
This keeps the product easy to understand and the engine independently reusable.
Non-goals
glacierdb is not:
- an ORM;
- a REST framework;
- a UI framework;
- an application server;
- a wrapper around PostgreSQL, SQLite or another database;
- a scripting runtime.
The database engine also intentionally does not depend on a graphical interface.
Relationship to the openglacier project
glacierdb is part of the broader openglacier ecosystem.
The architecture is deliberately split between:
openglacier Core
│
└── reusable engine and runtime
glacierdb : openglacier db
│
└── database distribution
openglacier
│
└── main initiative to a broader ecosystem and distributed architecture
This allows the database engine to evolve independently from the way it is distributed and presented to users.
For the broader openglacier project and architecture:
https://github.com/openglacier/openglacier
For the database engine:
https://github.com/openglacier/core
License
MIT
The 80 days challenge
I wanted something very simple, easily auditable by IT professionals, so I set myself an arbitrary target: the
first version had to be ready on Friday, 28th August 2026.
It was. openglacier v1 was released on that date, after 80 days of work.
Those 80 days did not start from a blank page. They took up the first concepts of openglacier, defined in 2023, and the road from there went through three engines:
- A query caching engine. At the origin, openglacier was only that: a layer keeping the results of queries.
- A small transactional engine, built on redb, an embedded key-value store written in pure Rust. A project I like very much.
- A pure event-driven database engine, with its own persistent storage backend, Glacier: today’s openglacier-core, on which glacierdb is built.
Each step kept what the previous one had taught, and the result is still meant to stay small enough to be read, understood and audited.
About the creator
An Enian, full stack developer, and digital activist. roots stand at the forefront of a tech revolution, fighting against the tide of mass consumption and environmental hypocrisy in the digital world. His mission? To build sustainable, ethical, and transparent technologies that empower users without exploiting the planet or misleading the public through greenwashing and planned obsolescence.
Together, we can create a future where digital innovation
serves both people and the planet — not profit at any cost. Let’s code a greener, fairer tomorrow.
-antonroots
The Manifesto
An initiative for healthier digital ecosystems.
Technology should deserve people’s trust.
openglacier is an initiative to design healthier digital ecosystems.
It is built around a simple belief:
Technology should be transparent.
Technology should be sustainable.
Technology should remain interoperable.
Technology should empower people—not lock them in.
This repository is not a software project.
It is the public manifesto that defines the principles, philosophy and long-term vision behind the openglacier initiative.
Why?
Today’s digital world is increasingly:
- fragmented
- opaque
- disposable
- vendor-dependent
- unnecessarily complex
openglacier explores another direction.
One where existing technologies communicate instead of competing.
One where devices remain useful longer.
One where people understand their own digital ecosystem.
One where trust is earned through transparency.
The Manifesto
Foundations
The Ecosystem
Experience
Looking Forward
Core Ideas
Everything is represented as an object.
Resources provide capabilities.
Agents transform capabilities into actions.
Missions coordinate objectives.
The workspace makes the ecosystem understandable.
Communication creates intelligence.
Trust emerges from transparency.
Not another platform
openglacier does not aim to replace existing technologies.
It aims to connect them.
The ecosystem is more important than any individual product.
The Long-Term Goal
The success of openglacier will not be measured by the number of installations.
It will be measured by the number of ideas that survive it.
If, one day, transparency, interoperability, sustainability and trust become natural expectations for every digital ecosystem, then the initiative will have fulfilled its purpose.
Build technology that deserves people’s trust.
Why
Technology should deserve people’s trust.
Why this initiative exists
Computing has transformed the world.
It has connected people, accelerated innovation and made knowledge accessible on an unprecedented scale.
Yet, somewhere along the way, we started measuring progress by consumption instead of value.
More applications. More cloud services. More powerful hardware. More subscriptions. More artificial intelligence.
More of everything.
But not necessarily more understanding, more trust or more freedom.
Technology should help people achieve more with less, not require more to achieve the same.
A digital world built to be replaced
Modern computing has become increasingly disposable.
Devices that still work are considered obsolete. Software is abandoned long before its purpose disappears. Entire ecosystems depend on services that users neither control nor fully understand.
Innovation is too often associated with replacement instead of improvement.
We believe technology deserves a longer life.
Hardware should remain useful. Software should remain understandable. Knowledge should remain transferable.
Progress should build upon what already exists instead of constantly discarding it.
Communication creates intelligence
No individual system can solve every problem.
Specialized tools already exist for communication, collaboration, storage, automation, finance, engineering, healthcare and countless other domains.
Their value does not come from replacing one another.
It comes from their ability to communicate.
Intelligence does not emerge from isolated systems.
It emerges from interactions.
Just as a single neuron is not intelligent on its own, digital systems become more valuable when they exchange information through open, understandable and trusted communication.
Trust cannot be purchased
Trust is not a feature.
It is the result of transparency.
People should understand how their digital ecosystem works.
Organizations should understand where their data flows.
Administrators should understand what systems are doing.
Artificial intelligence should assist without becoming an unquestionable authority.
Trust grows when technology can be observed, inspected and explained.
Not when it asks to be believed.
Digital sovereignty starts with understanding
Digital sovereignty is often reduced to where data is stored.
We believe it starts much earlier.
People cannot control what they do not understand.
Organizations cannot manage risks they cannot observe.
Privacy, security and resilience should emerge from architecture, not from endless configuration.
Understanding creates autonomy.
Autonomy creates sovereignty.
Sustainability is a responsibility
Technology should respect both people and the resources it depends on.
Extending the useful life of existing hardware is often more valuable than replacing it.
Reducing unnecessary complexity is as important as reducing unnecessary energy consumption.
The most sustainable technology is often the one that already exists.
Innovation should not encourage constant replacement.
It should encourage better use.
Open means more than open source
For us, openness is not only about source code.
It is about standards.
It is about interoperability.
It is about transparency.
It is about collaboration.
Open ecosystems allow individuals and organizations to choose their own technologies without sacrificing their independence.
Technology should connect ecosystems, not imprison them.
Why openglacier
openglacier is an initiative built around a simple belief:
Technology should deserve people’s trust.
It exists to promote digital ecosystems that are transparent, sustainable and interoperable.
It does not seek to replace existing tools.
It seeks to help them communicate.
It does not seek to make technology invisible.
It seeks to make technology understandable.
If one day initiatives like openglacier are no longer necessary, it will mean the digital world has finally embraced these principles.
Until then, this is our contribution.
Vision
Your digital ecosystem, under your control.
A different way to think about technology
We envision a future where technology is no longer defined by products, platforms or vendors.
Instead, it is defined by ecosystems.
An ecosystem where people, organizations, devices, services and artificial intelligence collaborate through open and transparent communication.
Technology should adapt to individuals.
Individuals should never have to adapt to technology.
A single workspace
Digital life should not be fragmented across countless interfaces.
Email, storage, artificial intelligence, business applications, automation, connected devices and future technologies should remain independent while being accessible through a consistent workspace.
Changing a provider should not require changing the way people work.
Your workspace should remain yours.
Your tools should remain your choice.
Every technology has a place
Innovation does not begin by replacing what already works.
It begins by understanding how existing technologies can work together.
Modern cloud services, local infrastructure, legacy systems, open source software and future technologies all have value.
A digital ecosystem should not discriminate between old and new.
It should allow every capable technology to contribute.
Communication before automation
Automation is valuable.
Artificial intelligence is valuable.
But neither creates value without communication.
The future belongs to ecosystems where systems understand each other through open standards and transparent interactions.
Communication is the foundation upon which intelligence can emerge.
Not the other way around.
Trust through transparency
Technology should never ask for blind trust.
Every action should be observable.
Every automation should remain understandable.
Every decision should be explainable.
Transparency is not only a security requirement.
It is the foundation of long-term trust.
Sustainability by design
The future of computing cannot rely on endless replacement.
Hardware should remain useful for as long as possible.
Software should continue to evolve without forcing unnecessary upgrades.
Digital ecosystems should minimize waste by making better use of existing resources.
Sustainability is not a feature.
It is a design principle.
Digital sovereignty for everyone
Digital sovereignty should not be reserved for experts.
Every individual and every organization should be able to understand their digital ecosystem.
Security, privacy and resilience should not depend on technical expertise.
They should emerge naturally from transparent architectures and open standards.
Technology should empower people to make informed decisions.
Artificial intelligence as a collaborator
Artificial intelligence is becoming an essential part of modern computing.
We envision AI as a collaborator rather than a replacement.
Its purpose is to extend human capabilities, simplify complex tasks and assist decision-making while remaining transparent and accountable.
Technology should always help people.
People should never become dependent on technology.
An initiative, not a destination
openglacier is not the destination.
It is an initiative towards a healthier digital future.
A future where interoperability matters more than lock-in.
Where trust matters more than novelty.
Where sustainability matters more than consumption.
Where communication matters more than isolation.
If one day these principles become the norm, the initiative will have fulfilled its purpose.
Until then, we continue building.
Philosophy
Technology should deserve people’s trust.
Our philosophy
Technology should improve people’s lives without demanding their attention.
The best technology is often the one that quietly enables people to achieve their goals while remaining understandable, observable and respectful of their choices.
We believe that digital ecosystems should be built upon principles that remain relevant regardless of technologies, vendors or trends.
These principles guide every decision made within the openglacier initiative.
Trust is earned
Trust should never be assumed.
It must be earned through transparency, consistency and honesty.
Technology should explain itself.
Reality should remain observable.
Users should understand what systems do, why they do it and how they interact.
Trust begins where opacity ends.
Communication creates intelligence
No system is intelligent in isolation.
Intelligence emerges through communication.
People, organizations, devices, services and artificial intelligence become more valuable when they collaborate through open and understandable interactions.
Communication is not a feature.
It is the foundation.
Reality precedes interpretation
Reality exists independently of the way we choose to observe it.
A Workspace owns its resources.
Resources represent facts, objects, services and information that continue to exist regardless of any application.
Applications do not own reality.
They give meaning to it.
Different applications may organize the same resources around different purposes.
A dashboard highlights.
A planner organizes.
An assistant explains.
A monitoring application observes.
Each reveals a different perspective of the same Workspace.
Applications may evolve.
Applications may disappear.
The Workspace remains.
Meaning belongs to applications.
Reality belongs to the Workspace.
Simplicity is respect
Simplicity is not the absence of functionality.
It is the absence of unnecessary complexity.
Every interface should reduce cognitive effort.
Every interaction should feel natural.
Every component should have a clear purpose.
Technology should be accessible to experts without excluding everyone else.
Transparency creates sovereignty
People cannot control what they cannot observe.
Organizations cannot manage what they cannot understand.
Digital sovereignty begins with visibility.
Transparency is not only a security principle.
It is a human principle.
Sustainability is a design choice
Every device still capable of contributing deserves the opportunity to do so.
Every existing technology deserves consideration before replacement.
Innovation should extend value instead of accelerating consumption.
Sustainability is measured by longevity, not novelty.
Open is a commitment
Open means more than publishing source code.
It means embracing open standards.
It means encouraging interoperability.
It means avoiding unnecessary dependencies.
It means allowing people to choose their technologies without losing control over their ecosystem.
Openness protects freedom.
Technology serves people
Technology exists to assist individuals.
Not to replace them.
Not to control them.
Not to demand their constant attention.
Artificial intelligence should extend human capabilities.
Automation should remove repetitive work.
Software should create time for what truly matters.
Organizations should trust people
Technology should not exist to increase surveillance.
It should exist to improve collaboration.
Organizations become stronger when individuals understand the systems they use and are trusted to make informed decisions.
Responsibility grows through understanding.
Not through control.
Failure is part of reality
Systems fail.
Networks become unavailable.
Hardware eventually breaks.
Software contains defects.
A resilient ecosystem does not hide failure.
It acknowledges it, explains it and recovers from it.
Honest systems inspire confidence.
Perfect systems do not exist.
Neutrality enables freedom
Technology should remain neutral.
Users decide which services they trust.
Organizations choose the technologies that fit their needs.
Artificial intelligence remains a collaborator, not an authority.
The role of openglacier is not to make choices on behalf of its users.
Its role is to make those choices possible.
Humility over hype
Technology does not need to be louder to become better.
Progress does not require constant reinvention.
Reliable solutions often outlive fashionable ones.
Innovation should solve real problems.
Not create new ones.
Our responsibility
The choices we make today shape the digital ecosystems of tomorrow.
Every architectural decision.
Every interface.
Every protocol.
Every line of code.
They should all contribute to a future that is more transparent, more sustainable and more respectful of the individuals who depend on technology every day.
Software will evolve.
Technologies will change.
Principles endure.
This is the philosophy that guides the openglacier initiative.
Principles
Good principles outlive technologies.
These principles guide every decision within the openglacier initiative.
When technology evolves, principles remain.
Trust before features
Technology should deserve people’s trust before earning their attention.
Transparency before automation
People should understand what systems do before asking them to automate anything.
Communication creates intelligence
Intelligence emerges from meaningful interactions, not from isolated systems.
Simplicity is a feature
Complexity is a cost.
Reduce it whenever possible.
Build less, connect more
Existing technologies already solve many problems.
Prefer connecting them over replacing them.
Sustainability is innovation
The most sustainable technology is often the one that already exists.
Longevity over novelty
Reliable technologies deserve a long life.
Newer is not always better.
Open by design
Open standards create stronger ecosystems than proprietary protocols.
Neutrality empowers choice
Technology should not decide which services people use.
People decide.
Freedom through interoperability
Users should always be free to replace technologies without rebuilding their ecosystem.
Privacy by architecture
Privacy should emerge naturally from the way systems are designed.
Security through simplicity
Simple systems are easier to understand, audit and secure.
Explainability matters
Every important action should be understandable.
Technology should never become a black box.
Honest systems build confidence
Failures should be visible.
Success should be measurable.
Technology should never pretend.
Failure is information
Failures are part of reality.
Good systems recover instead of hiding them.
Observe before acting
Understanding a system always comes before modifying it.
Individuals remain responsible
Technology assists.
Individuals decide.
Responsibility cannot be delegated.
AI augments people
Artificial intelligence extends human capabilities.
It never replaces human responsibility.
Organizations should trust people
Technology should strengthen collaboration, not surveillance.
Every device deserves a chance
Useful hardware should remain useful.
Obsolescence should not be a business model.
Every technology has value
Modern and legacy technologies can coexist.
Interoperability is more important than age.
One workspace, many ecosystems
Users should experience consistency even when technologies evolve.
Standards build trust
Use standards created for the public interest whenever possible.
Quiet technology
The best technology often goes unnoticed.
It simply works.
Durable software
Software should evolve without forcing unnecessary replacement.
Empower, don’t lock in
Technology should increase freedom, not dependence.
Progress through understanding
People cannot improve what they do not understand.
Consistency over fashion
Design trends change.
Principles remain.
Measure value, not consumption
More hardware.
More cloud.
More software.
More AI.
None of these automatically create more value.
Technology serves life
Technology is a means.
Never the purpose.
Build for tomorrow
Every decision should remain meaningful years from now.
Think beyond the next release.
The initiative comes first
Projects evolve.
Repositories change.
Technologies disappear.
The initiative remains.
Leave things better
Every contribution should improve the ecosystem for the next contributor.
Technology should deserve people’s trust
Everything begins here.
Glossary
Shared words create shared understanding.
The following terms define the vocabulary used throughout the openglacier initiative.
These definitions are intentionally technology-agnostic and remain valid regardless of implementation.
Agent
An autonomous software entity able to perform tasks, collaborate with other agents and interact with resources on behalf of an individual or an organization.
An agent assists.
It does not replace responsibility.
Automation
A predefined sequence of actions executed without continuous human intervention.
Automations remain observable, explainable and interruptible.
Capability
A function or service that a resource makes available to the ecosystem.
Capabilities describe what a resource can do, not how it does it.
Collaboration
The ability of multiple individuals, agents and resources to achieve a common objective through communication.
Communication
The exchange of information between individuals, organizations, devices, services and artificial intelligence.
Communication is the foundation upon which collaboration and intelligence emerge.
Connector
A software component responsible for connecting a resource to the ecosystem.
A connector translates external technologies into a common language understood by openglacier.
Core
The logical heart of the ecosystem.
It coordinates communication, identities, events and relationships between connected resources.
The Core defines behavior.
It does not define the user interface.
Digital Ecosystem
A coherent environment composed of individuals, organizations, devices, services, agents and resources collaborating through open communication.
Event
A meaningful change occurring within the ecosystem.
Events describe what happened.
They never describe what should happen.
Gateway
The secure communication layer connecting distributed parts of the ecosystem.
Gateways allow ecosystems to communicate while preserving security, privacy and sovereignty.
Hub
The primary workspace through which individuals interact with their digital ecosystem.
The Hub visualizes the ecosystem.
It does not own it.
Identity
A trusted representation of an individual, organization, service or machine.
An identity defines who participates in the ecosystem.
Individual
Any autonomous participant within the ecosystem.
An individual may be a human, an organization, an artificial intelligence or any other entity capable of interacting responsibly.
Initiative
The collective effort behind openglacier.
An initiative is larger than a software project.
It represents shared principles, values and long-term goals.
Mission
A coordinated objective involving one or more individuals, agents and resources.
A mission produces outcomes.
It is not a conversation.
It is not a workflow.
It is purposeful work.
Object
A visible representation of an entity within the workspace.
Every object has:
- an identity
- a state
- capabilities
- relationships
- events
- actions
Objects are the fundamental building blocks of the workspace.
Organization
A group of individuals collaborating toward common objectives.
Organizations participate in the ecosystem as first-class entities.
Resource
Any connected entity providing one or more capabilities.
Examples include:
- devices
- storage systems
- software
- online services
- artificial intelligence
- sensors
- infrastructure
Resources are connected.
They are not owned by openglacier.
Sovereignty
The ability of individuals and organizations to understand, control and evolve their own digital ecosystem.
Sovereignty begins with transparency.
Standard
A publicly defined specification created for the common interest.
Open standards promote interoperability, longevity and trust.
Storage
A resource responsible for preserving information.
A storage system remains a resource like any other.
Transparency
The ability to observe, understand and explain the behavior of a system.
Transparency creates trust.
Trust
Confidence earned through transparency, consistency and openness.
Trust can never be assumed.
It must always be earned.
Workspace
The coherent environment through which an individual observes and interacts with the ecosystem.
A workspace is composed of living objects rather than isolated pages.
Axioms
Simple truths from which architecture emerges.
The philosophy explains why.
The axioms describe what remains true.
Every architectural decision should respect these principles.
Technology should deserve people’s trust.
Trust cannot be assumed.
It must be earned through transparency, consistency and honesty.
Communication creates intelligence.
No person, device, service or artificial intelligence is truly valuable in isolation.
Intelligence emerges through communication.
Workspaces own reality.
A Workspace is the source of truth.
Resources belong to the Workspace.
Applications do not.
Resources outlive applications.
Resources represent reality.
Applications may evolve.
Applications may disappear.
Resources remain.
Apps give meaning to data.
Applications organize resources, agents and interactions around a specific purpose.
They interpret reality.
They do not own it.
Agents collaborate.
Agents are collaborators.
They reason.
They assist.
They communicate.
They never replace human responsibility.
Knowledge exists at different levels.
Some knowledge belongs to the Workspace.
Some knowledge belongs to each Agent.
Sharing is intentional.
Isolation is sometimes necessary.
Activities tell the story.
Every meaningful action contributes to the history of a Workspace.
Activities provide understanding.
Not surveillance.
Transparency creates confidence.
People trust what they can observe.
Systems should explain themselves.
Reality should remain visible.
Simplicity is respect.
Complexity is a cost.
Every feature should justify its existence.
Every interaction should have a purpose.
Failure is normal.
Failure is part of reality.
Resilient systems acknowledge failure.
They explain it.
They recover from it.
Open protects freedom.
Open standards preserve independence.
Interoperability creates choice.
Choice creates freedom.
Sustainability begins with longevity.
Technology should extend the life of existing systems whenever possible.
Innovation should preserve value before replacing it.
Architecture follows principles.
Software changes.
Technologies evolve.
Architectures adapt.
Principles endure.
Workspace
A workspace is a living collection of objects, not a collection of pages.
What is a workspace?
A workspace is the visible representation of a digital ecosystem.
It is where individuals observe, understand and interact with everything connected to their ecosystem.
Unlike traditional software, a workspace is not organized around pages or menus.
It is organized around objects.
Each object represents a real entity capable of communicating, evolving and collaborating with others.
One workspace, one ecosystem
An individual interacts with a single workspace regardless of the number of connected technologies.
Email providers may change.
Storage systems may evolve.
Artificial intelligence may improve.
Devices may come and go.
The workspace remains familiar.
Technology evolves.
The workspace endures.
Objects, not pages
Traditional applications organize information into pages.
Dashboards.
Settings.
Lists.
Forms.
Navigation.
The openglacier workspace follows a different model.
Everything visible is an object.
An object may represent:
- an individual
- an organization
- an agent
- a resource
- a mission
- a storage system
- a device
- a service
Objects become the primary way people understand their ecosystem.
Living objects
Objects are never static.
They continuously reflect the current state of the ecosystem.
A storage object displays its health.
An agent displays its activity.
A mission displays its progress.
A device displays its availability.
Objects always communicate their current state before users ask for it.
The workspace is alive.
Zoom, don’t navigate
Moving through the workspace should feel natural.
Selecting an object does not open another application.
It simply reveals more context.
Every interaction follows the same principle:
Observe.
Select.
Zoom.
Understand.
Each zoom level exposes additional information while preserving context.
Users never feel lost.
Relationships over hierarchy
Digital ecosystems are networks.
Not trees.
Objects are connected through meaningful relationships.
Resources support missions.
Agents collaborate with individuals.
Organizations own identities.
Devices host services.
Storage systems preserve information.
Understanding these relationships is often more valuable than understanding technical details.
Calm by default
The workspace should never compete for attention.
Notifications remain meaningful.
Animations explain changes.
Colors communicate state.
Visual noise is minimized.
The workspace should feel calm, even when the ecosystem behind it is complex.
Honest interfaces
The workspace never hides reality.
Unavailable resources remain visible.
Failed missions remain observable.
Disconnected devices continue to exist.
Failures are explained instead of ignored.
Technology should remain honest.
Built for everyone
The workspace should remain approachable regardless of technical expertise.
Experts should appreciate its transparency.
Beginners should appreciate its simplicity.
People should understand their ecosystem without needing to understand the technologies behind it.
A place for collaboration
A workspace is more than an interface.
It is where individuals, organizations, agents and resources collaborate.
Communication happens through objects.
Actions happen through missions.
Capabilities emerge through resources.
Everything belongs to the same ecosystem.
The workspace belongs to the individual
The workspace is independent from vendors.
Independent from hardware.
Independent from cloud providers.
Independent from technologies.
It evolves alongside its owner.
The ecosystem changes.
The workspace remains.
Objects
Everything visible in openglacier is an object.
Why objects?
Digital ecosystems are made of many different technologies.
Devices.
Applications.
Storage systems.
Artificial intelligence.
Organizations.
People.
Although they appear different, they all share something in common.
They exist.
They evolve.
They communicate.
They collaborate.
In openglacier, every meaningful entity is represented as an object.
Objects provide a common language for understanding digital ecosystems.
A common representation
Objects abstract implementation details.
Whether a resource is:
- a NAS
- a smartphone
- an email account
- an AI model
- a calendar
- a company
- a Raspberry Pi
- a cloud service
it is first represented as an object.
The object remains consistent even if the underlying technology changes.
Objects are alive
Objects continuously reflect reality.
Their state evolves.
Their relationships evolve.
Their capabilities evolve.
Their activity evolves.
Objects are never static.
They remain present even when temporarily unavailable.
The ecosystem remembers them.
Presence
Presence is different from availability.
A disconnected device is still present.
An offline storage system is still present.
An inactive agent is still present.
A completed mission is still present.
Objects never disappear simply because communication has been interrupted.
Presence preserves understanding.
Every object has an identity
Every object can be uniquely identified.
Identity remains stable throughout the object’s lifetime.
Names may change.
Locations may change.
Capabilities may evolve.
Identity remains.
Relationships depend on identity.
Never on appearance.
Every object has a state
Objects continuously expose their current state.
Examples include:
- available
- unavailable
- busy
- synchronizing
- degraded
- paused
- completed
State represents reality.
It is never decorative.
Every object exposes capabilities
Capabilities describe what an object can do.
Not how it does it.
Examples:
A storage object may provide:
- Store
- Retrieve
- Search
An AI object may provide:
- Analyze
- Summarize
- Translate
A calendar object may provide:
- Schedule
- Update
- Notify
Capabilities allow collaboration between objects.
Every object emits events
Objects communicate through events.
An event describes something that happened.
Examples:
Storage became unavailable.
Mission completed.
Device connected.
Identity authenticated.
Agent started.
Events describe facts.
Never intentions.
Every object supports actions
Actions represent intentional interactions.
Unlike events, actions are initiated by individuals, agents or automations.
Examples:
Connect.
Disconnect.
Rename.
Synchronize.
Share.
Start.
Pause.
Actions modify the ecosystem.
Events describe the result.
Every object has relationships
Objects never exist in isolation.
They belong to a network.
A mission uses resources.
An organization owns identities.
A storage preserves files.
An agent assists an individual.
Relationships explain the ecosystem.
More effectively than technical documentation.
Objects are observable
Every object should answer simple questions.
What are you?
What are you doing?
What can you do?
Who depends on you?
What happened recently?
Can I trust your current state?
Objects exist to make complex ecosystems understandable.
Objects are honest
Objects never hide reality.
Unavailable objects remain visible.
Errors remain observable.
History remains accessible.
An object should never pretend to be healthy when it is not.
Honesty builds trust.
Objects are durable
Objects outlive technologies.
Replacing an email provider should not destroy the email object.
Replacing a NAS should not destroy the storage object.
Replacing an AI model should not destroy the AI object.
Objects preserve continuity while technologies evolve.
Objects are collaborative
Objects do not compete.
They communicate.
They exchange capabilities.
They share information.
They participate in missions.
Intelligence emerges from their collaboration.
Objects define the workspace
The workspace is nothing more than a living collection of objects.
Understanding objects means understanding the ecosystem.
Everything else is presentation.
Resources
Resources extend the ecosystem through capabilities.
What is a resource?
A resource is any entity capable of providing one or more capabilities to the digital ecosystem.
A resource exists independently from openglacier.
It is not created by the ecosystem.
It simply becomes part of it.
Resources may represent physical devices, software, online services or intelligent systems.
Their implementation is irrelevant.
Only their capabilities matter.
Resources already exist
The purpose of openglacier is not to replace existing technologies.
It is to connect them.
Resources already exist around us.
Our phones.
Our computers.
Our NAS.
Our calendars.
Our email providers.
Our databases.
Our AI models.
Our IoT devices.
Our cloud services.
The ecosystem becomes more valuable as these resources learn to communicate.
Resources are first-class citizens
Resources are not plugins.
They are not extensions.
They are not integrations.
Resources are fundamental members of the ecosystem.
Every resource participates through the same object model.
Every resource can communicate.
Every resource can expose capabilities.
Every resource can collaborate.
Resources are represented as objects
Within the workspace, every resource becomes an object.
The object provides a consistent representation regardless of the underlying technology.
Changing a provider should not change how users understand their ecosystem.
The object remains.
The implementation evolves.
Resources expose capabilities
Resources do not expose implementation details.
They expose capabilities.
For example:
An email provider may expose:
- Send
- Receive
- Search
A storage system may expose:
- Store
- Retrieve
- Synchronize
An AI model may expose:
- Analyze
- Summarize
- Translate
Capabilities define what resources contribute to the ecosystem.
Resources communicate through events
Resources continuously inform the ecosystem about meaningful changes.
Examples include:
A device becomes available.
Storage reaches capacity.
A calendar event is created.
An AI model finishes processing.
A backup completes.
Resources communicate facts.
The ecosystem reacts.
Resources remain independent
Resources keep their own lifecycle.
They remain owned by their respective vendors, organizations or individuals.
openglacier never assumes ownership.
It simply provides a common language through which resources collaborate.
Resources can be replaced
No resource is irreplaceable.
Users remain free to replace technologies whenever they choose.
An email provider may change.
A storage system may evolve.
A cloud provider may disappear.
The surrounding ecosystem continues to function.
Resources should be interchangeable whenever possible.
Local and remote are equals
Resources may execute:
- locally
- remotely
- on-premises
- in the cloud
- at the edge
Location does not change their role within the ecosystem.
Capabilities matter.
Not geography.
Resources should be observable
Every resource should answer simple questions.
What are you?
What can you do?
What is your current state?
What happened recently?
Who depends on you?
Can you be trusted?
Resources contribute to understanding.
Not complexity.
Resources collaborate
Resources rarely operate alone.
Storage supports agents.
Agents consume AI.
Missions orchestrate resources.
Organizations manage identities.
Communication transforms isolated technologies into an ecosystem.
Resources evolve
Capabilities may grow.
Implementations may change.
Providers may disappear.
Protocols may evolve.
The ecosystem should continue to function without forcing users to redesign their workspace.
Resources evolve.
The ecosystem adapts.
Resources are connected, not consumed
Modern software often encourages replacing one service with another.
openglacier encourages something different.
Connect.
Understand.
Collaborate.
Improve.
Resources are valuable because they contribute to the ecosystem, not because they belong to it.
Synthesis
| Concept | Exists independently of openglacier | Represented in the Workspace | Provides capabilities |
|---|---|---|---|
| Resource | ✅ Yes | Through an Object | ✅ Yes |
| Connector | ❌ No | ❌ No | ❌ No |
| Object | ❌ No (representation) | ✅ Yes | Reflects the Resource’s capabilities |
Agents
Agents transform capabilities into actions.
What is an agent?
An agent is an autonomous object capable of observing, deciding and acting within the digital ecosystem.
Agents use the capabilities exposed by resources to achieve meaningful objectives.
They assist individuals.
They never replace responsibility.
Agents are objects
Every agent is represented as an object within the workspace.
Like every other object, an agent has:
- an identity
- a presence
- a state
- capabilities
- relationships
- events
- actions
- history
Agents follow the same object model as the rest of the ecosystem.
Autonomy defines an agent
An agent is not defined by artificial intelligence.
It is defined by its ability to operate autonomously.
Some agents follow predefined rules.
Others use artificial intelligence.
Others combine both approaches.
Autonomy matters.
Implementation does not.
Agents observe
Agents continuously observe the ecosystem.
They react to meaningful events.
Examples include:
- a new email arrives
- storage reaches capacity
- a mission starts
- a device becomes unavailable
- a schedule changes
Observation always precedes action.
Agents decide
Agents transform observations into decisions.
Some decisions are deterministic.
Others may require reasoning.
Some always require human approval.
Decision-making should remain transparent and explainable.
Agents act
Agents perform actions using the capabilities provided by resources.
Examples include:
- sending notifications
- creating backups
- organizing information
- generating reports
- coordinating missions
- communicating with other agents
Agents do not own capabilities.
They orchestrate them.
Agents collaborate
Agents rarely work alone.
They communicate with:
- individuals
- organizations
- resources
- other agents
Communication enables specialization.
Specialization enables scalability.
Agents remain observable
Every agent should answer simple questions.
What are you doing?
Why are you doing it?
What are you waiting for?
What happened recently?
Can your actions be interrupted?
Autonomy should never become opacity.
Human responsibility
Agents assist.
Individuals decide.
Organizations govern.
Responsibility always remains identifiable.
No autonomous action should make responsibility impossible to determine.
AI is one kind of agent
Artificial intelligence extends what agents can achieve.
It does not redefine what an agent is.
An AI model is simply one possible implementation.
The ecosystem remains independent of any specific AI technology.
Agents learn
Some agents may improve over time.
Others remain entirely deterministic.
Learning is optional.
Transparency is not.
Agents participate in missions
Agents contribute to missions by coordinating actions across multiple resources.
A mission may involve:
- one individual
- multiple agents
- several resources
- one or more organizations
Agents help missions progress.
They do not define their purpose.
Replaceable by design
Agents should remain interchangeable whenever possible.
Replacing one implementation with another should not require redesigning the ecosystem.
Capabilities remain.
Behavior may improve.
The ecosystem continues.
Trustworthy autonomy
Autonomy is valuable only when it deserves trust.
Agents should remain:
- observable
- explainable
- interruptible
- accountable
Trust is earned through transparency.
Never through complexity.
Missions
Missions transform intentions into outcomes.
What is a mission?
A mission is a coordinated objective carried out by individuals, agents and resources.
A mission exists to achieve a meaningful outcome.
It defines why something is being done.
Not how.
Missions are objects
Every mission is represented as an object within the workspace.
Like every other object, a mission has:
- an identity
- a presence
- a state
- relationships
- events
- actions
- history
Missions follow the same object model as every other entity in the ecosystem.
Purpose before process
Traditional workflows define sequences of steps.
A mission defines an objective.
How that objective is achieved may evolve over time.
The purpose remains.
Missions coordinate collaboration
A mission brings together the right participants.
These may include:
- individuals
- organizations
- agents
- resources
Every participant contributes according to its capabilities.
Missions orchestrate capabilities
A mission does not perform actions itself.
Instead, it coordinates the capabilities provided by participating resources and agents.
Resources provide capabilities.
Agents execute actions.
Individuals make decisions.
The mission gives direction.
Missions evolve
Objectives rarely remain static.
Requirements change.
Participants change.
Resources become available or unavailable.
A mission adapts while preserving its purpose.
Missions are observable
Every mission should answer simple questions.
What is the objective?
Who is participating?
What is happening now?
What has already been completed?
What remains to be done?
Transparency enables collaboration.
Missions communicate
A mission continuously emits meaningful events.
Examples include:
- mission created
- participant joined
- resource assigned
- objective completed
- mission paused
These events allow the ecosystem to react and collaborate.
Missions preserve context
A mission remembers its history.
It records decisions.
It records participation.
It records outcomes.
Understanding why something happened is often more valuable than knowing that it happened.
Missions remain independent
A mission should not depend on a specific technology.
Replacing an AI model.
Changing a storage provider.
Migrating an email service.
None of these should redefine the mission.
Technology supports the mission.
It never defines it.
Missions may span ecosystems
A mission is not limited to one device or one service.
It may coordinate resources distributed across multiple environments.
The ecosystem behaves as one.
Human responsibility
Agents assist.
Resources provide capabilities.
Individuals remain responsible for the mission and its outcome.
Responsibility always remains identifiable.
Missions conclude
Every mission eventually reaches an outcome.
It may succeed.
It may fail.
It may be cancelled.
Completion does not erase its history.
Every mission contributes to understanding the ecosystem.
User Interface
The interface should reveal the ecosystem, not hide it.
The interface is not the product
The user interface is the visible expression of the workspace.
It does not define the ecosystem.
It simply helps individuals understand and interact with it.
The interface should always remain secondary to the objects it presents.
Objects before pages
Traditional software is organized into pages.
Dashboards.
Settings.
Forms.
Menus.
The openglacier interface is organized around objects.
Users interact with objects.
Not pages.
Zoom, don’t navigate
Navigation should feel natural.
Selecting an object reveals more context.
Not another application.
Not another disconnected page.
Each interaction should preserve orientation and context.
Understanding grows through exploration.
Not navigation.
One interaction model
Every object follows the same interaction principles.
Observe.
Understand.
Act.
Regardless of whether the object represents:
- an individual
- a resource
- an agent
- a mission
- an organization
Users should not need to learn different interfaces for different technologies.
Calm interfaces
Good interfaces do not compete for attention.
Animations explain changes.
Notifications communicate important events.
Colors describe state.
Silence is preferred over noise.
Reality over decoration
Every visual element should communicate meaning.
Status indicators represent real states.
Animations represent real transitions.
Icons represent real entities.
Decoration should never replace information.
Honest interfaces
Disconnected resources remain visible.
Failed missions remain visible.
Unavailable devices remain visible.
The interface should explain reality.
Never hide it.
Progressive complexity
The interface should reveal complexity only when it becomes useful.
Beginners should immediately understand the ecosystem.
Experts should always be able to explore deeper.
Complexity should emerge through interaction.
Never through overload.
Consistency creates confidence
Objects behave consistently.
Actions appear consistently.
States are represented consistently.
Predictability builds confidence.
Explain before automate
People should understand what is happening before being asked to trust automation.
Interfaces should always answer:
What happened?
Why?
What will happen next?
Accessibility by design
The interface should remain usable regardless of:
- age
- technical expertise
- abilities
- devices
Accessibility is not an optional feature.
It is part of good design.
Responsive by nature
The workspace should adapt naturally to different devices.
Large screens provide more context.
Small screens preserve the same concepts.
The interaction model remains identical.
Presence before activity
Objects remain visible even when inactive.
Presence does not depend on activity.
Understanding depends on continuity.
The interface should disappear
The best interface is the one users no longer notice.
People should focus on their ecosystem.
Not on the software itself.
Branding
A brand is not what it says. It is what people consistently experience.
More than a name
openglacier is not a company.
It is not a product.
It is not a platform.
It is an initiative that promotes a healthier, more transparent and more sustainable digital ecosystem.
Every project carrying the openglacier name should reflect these values.
A promise
The openglacier name represents a commitment.
A commitment to:
- openness
- transparency
- interoperability
- sustainability
- simplicity
- trust
Technology changes.
These commitments do not.
The meaning of the name
The name combines two complementary ideas.
Open represents:
- open standards
- open ecosystems
- open communication
- open collaboration
- open source
Glacier represents the health of our planet.
Like glaciers, digital ecosystems can slowly deteriorate without anyone noticing.
Technology should contribute to preserving both.
Trust is the brand
Trust is not created through marketing.
It is earned through consistent behavior.
Every decision.
Every interface.
Every interaction.
Every line of code.
Should reinforce trust.
Simplicity is respect
People should never feel excluded because technology is unnecessarily complex.
Simplicity is not the absence of capability.
It is the absence of unnecessary complexity.
Neutrality
openglacier does not promote specific vendors, providers or technologies.
The ecosystem remains open.
Individuals choose.
Organizations choose.
Freedom requires neutrality.
Sustainability
Technology should extend the life of existing devices whenever possible.
Replacing hardware should never be the default solution.
Digital sustainability is part of environmental sustainability.
Transparency
Technology should explain itself.
People should understand:
- what happens
- why it happens
- who is responsible
- what changed
Transparency creates confidence.
Consistency
The openglacier experience should feel familiar across every project.
Concepts remain consistent.
Vocabulary remains consistent.
Behavior remains consistent.
Consistency builds confidence.
Humility
openglacier does not aim to replace everything.
It aims to connect what already exists.
The ecosystem is more important than any individual technology.
A long-term vision
The initiative is designed to outlive technologies.
Success is not measured by adoption alone.
Success is measured by influence.
If the principles of openglacier inspire better digital ecosystems—even without using openglacier itself—the initiative has already succeeded.
A recognizable feeling
People may forget the logo.
They may forget the website.
They may even forget the name.
But they should remember how it felt.
Simple.
Honest.
Transparent.
Calm.
Trustworthy.
That is the openglacier brand.
Roadmap
The roadmap describes where the initiative is going, not when it will arrive.
The evolution of openglacier is guided by long-term objectives rather than release dates.
Priorities may evolve.
Principles do not.
Phase 1 — Foundations
Establish the common language of the ecosystem.
Objectives include:
- defining the architecture
- defining the object model
- documenting the initiative
- establishing shared vocabulary
- designing interoperability
- building trust through transparency
Success means everyone understands the vision.
Phase 2 — Core Ecosystem
Build the foundations that allow resources to communicate.
Objectives include:
- object lifecycle
- event system
- identities
- resources
- agents
- missions
- communication between components
Success means isolated technologies become a connected ecosystem.
Phase 3 — Workspace
Create a workspace capable of representing the ecosystem.
Objectives include:
- object visualization
- contextual interaction
- zoom-based navigation
- responsive experience
- accessibility
- calm interfaces
Success means people understand their ecosystem naturally.
Phase 4 — Intelligence
Enable collaboration between individuals, agents and artificial intelligence.
Objectives include:
- autonomous agents
- mission orchestration
- explainable automation
- collaborative intelligence
- contextual assistance
Success means intelligence emerges through communication.
Phase 5 — Sustainability
Extend the lifetime and value of digital ecosystems.
Objectives include:
- support for legacy hardware
- efficient resource usage
- local-first capabilities
- interoperability with existing technologies
- measurable digital sustainability
Success means technology creates less waste while delivering more value.
Phase 6 — Community
Grow an ecosystem of contributors, organizations and adopters.
Objectives include:
- documentation
- education
- governance
- open collaboration
- shared best practices
Success means the initiative becomes larger than its original creators.
Long-term vision
The ultimate objective is not to create another software platform.
It is to contribute to a healthier digital ecosystem.
An ecosystem where:
- technology deserves trust
- communication creates intelligence
- sustainability is built into design
- individuals remain free to choose
- organizations trust people
If these ideas continue to spread, the initiative has already succeeded.
Conversations
Every initiative begins with questions.
The openglacier initiative was not designed by starting with technology.
It began by asking simple questions.
The following conversation captures the ideas that shaped its philosophy.
Why does openglacier exist?
Because today’s digital world is becoming increasingly fragmented, opaque and disposable.
The objective is not to replace existing technologies.
It is to help them communicate.
What problem does openglacier solve?
The lack of communication between existing technologies.
Email.
Storage.
Artificial intelligence.
Enterprise software.
Cloud services.
Each already solves specific problems.
The missing piece is meaningful communication between them.
Why “open”?
Because openness goes beyond Open Source.
It also means:
- open standards
- open communication
- open ecosystems
- open collaboration
Why “glacier”?
A glacier reflects the health of our planet.
Likewise, digital ecosystems reveal the health of our digital society.
The goal is not only to build better software.
It is to contribute to a healthier digital ecosystem.
What is digital sovereignty?
Digital sovereignty is the ability to understand digital flows without requiring technical expertise.
People cannot control what they cannot understand.
Transparency comes first.
Is openglacier trying to replace existing software?
No.
Existing software already provides enormous value.
openglacier exists to connect technologies rather than replace them.
Why are communication and interoperability so important?
Because communication creates intelligence.
A single isolated system is limited.
Connected systems become capable of collaboration.
Intelligence emerges through communication.
Why focus on sustainability?
Because replacing working technology should not be the default solution.
Good software should extend the useful life of devices.
Digital sustainability contributes to environmental sustainability.
Should openglacier have opinions about technologies?
No.
The initiative remains neutral.
Individuals and organizations choose the technologies that best meet their needs.
Freedom requires neutrality.
What is trust?
Trust is earned through transparency.
Not through marketing.
Not through complexity.
Not through reputation.
People trust systems they can understand.
Who is the workspace designed for?
Everyone.
Developers.
System administrators.
Support teams.
Businesses.
Individuals.
Technology should not require expertise to become understandable.
What happens when something fails?
Failure remains visible.
Unavailable resources remain present.
Reality is never hidden.
Honest systems build confidence.
What should technology do?
Technology should deserve people’s trust.
What should organizations do?
Organizations should trust people.
Technology should strengthen collaboration.
Not surveillance.
Why are objects so important?
Because objects provide a common language for understanding digital ecosystems.
Resources.
Agents.
Missions.
Organizations.
Individuals.
Everything visible becomes an object.
Why define missions instead of workflows?
Workflows describe processes.
Missions describe purpose.
Purpose survives technological change.
Why define agents independently from AI?
Because autonomy is broader than artificial intelligence.
AI is one possible implementation.
Not the definition.
What makes an ecosystem healthy?
Transparency.
Communication.
Interoperability.
Sustainability.
Trust.
Freedom.
What does success look like?
The success of openglacier will not be measured by adoption alone.
It will be measured by influence.
If future technologies naturally embrace transparency, interoperability and trust, the initiative will already have succeeded.
What happens if openglacier disappears one day?
Then perhaps a new digital era has begun.
The initiative was never meant to become indispensable.
It was meant to help the digital ecosystem evolve.
What is the purpose of the initiative?
To help technology deserve people’s trust.
Culture
Technology explains systems. Culture explains intentions.
The openglacier initiative is built upon principles, architecture and open standards.
It is also accompanied by a small mythology.
These stories are not specifications.
They are not requirements.
They simply provide a shared language for expressing ideas that are sometimes difficult to describe with technical terms alone.
The Story of Enian-ka
Once upon a time, in the land of Numan-ka, there lived a curious creature named Numan-ka.
Numan-ka was known for his endless questions and his love for exploring the unknown.
One day, while wandering through the Whispering Woods, Numan-ka met a wise old owl named Enian-ka.
Enian-ka had seen many seasons and knew the secrets of transformation.
“Why do you wander so far, young Numan-ka?” asked Enian-ka.
“I seek to become like you,” replied Numan-ka, “to understand the world and grow wise!”
Enian-ka smiled and said,
“To become like me, you must first understand yourself. You must learn the ways of patience, curiosity and change.”
So Numan-ka listened, learned and grew.
Day by day, he practiced the owl’s ways—watching silently, thinking deeply and adapting to the world around him.
Slowly, he began to change—not just in body, but in spirit.
And so, Numan-ka became Enian-ka.
Not by magic,
but through wisdom,
effort,
and the courage to transform.
Numan-ka
A Numan-ka is an individual at the beginning of a journey.
Curious.
Learning.
Questioning.
Discovering.
Every contributor starts as a Numan-ka.
Enian-ka
An Enian-ka is not defined by knowledge.
It is defined by understanding.
An Enian-ka shares knowledge freely.
Listens before speaking.
Explains before deciding.
Questions assumptions.
Learns continuously.
Helps others grow.
Being an Enian-ka is not a status.
It is a mindset.
The Journey
The objective is not to become powerful.
Nor to become an expert.
The objective is to better understand the world.
Transformation is continuous.
No one ever truly finishes the journey.
Raaka
The ultimate objective is to reach Raaka.
Raaka represents what is:
- true
- tangible
- concrete
- observable
- understandable
Raaka is reality without unnecessary interpretation.
It is the search for what genuinely exists rather than what we merely assume.
Technology should always strive toward Raaka.
Transparency is one path to Raaka.
Observation is another.
Understanding is the destination.
Anta-Raaka
Anta-Raaka represents the opposite path.
Assumptions without evidence.
Complexity without purpose.
Opacity instead of transparency.
Illusion instead of understanding.
Technology drifts toward Anta-Raaka whenever it becomes impossible to explain, verify or trust.
The purpose of openglacier is not merely to build software.
It is to continually move away from Anta-Raaka and toward Raaka.
A Shared Language
These words are not intended to replace technical terminology.
They simply provide a common language for expressing ideas shared by the community.
Like every culture, they evolve through the people who use them.
A Final Thought
Every initiative needs architecture.
Every ecosystem needs standards.
Every community needs culture.
Technology connects systems.
Culture connects people.
Books
Other openglacier books, each a book of its own, published inside this site.
The sources live in the repository under og-books/manifest/: this book in src/, and each sub-book in a folder
of its own (the_ogml_language/) with its own book.toml. A sub-book is built into a sub-folder of this site
(the_ogml_language/) and shares its theme. og-books/manifest/tools/build.sh builds the whole site.
License
The manifesto is published under the MIT License.
MIT License
Copyright (c) 2023-2026 openglacier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.