Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

openglacier
Your data equity
An initiative to design healthier digital ecosystems focused on trust, transparency

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

glacierdb logo

An event-first, schema-less document database built on the openglacier engine.

Latest release Build Tests Release targets

glacierdb 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:

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] }
usersagecityactivetagsprofile.countryprofile.score
Alice31Paristrueadmin, devFR9.5
Bob17LyonfalsedevFR7
Carol45Berlintrue(empty)DE8.25
Dan28ParistrueopsFR6
ordersuserproductqtypricestatusmonthregion
1AliceP1210.0paid2026-01EU
2AliceP2199.5paid2026-02EU
3BobP1510.0pending2026-01EU
4CarolP31250.0paid2026-02US
5DanP2399.5cancelled2026-01US
6CarolP1410.0paid2026-01US
productslabelcategoryembedding
P1Penoffice[1.0, 0.0, 0.0]
P2Printeroffice[0.9, 0.1, 0.0]
P3Phonemobile[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>]
  • on and from are 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 users returns every document.
  • The alias names the source. At the top level, u.age and age are the same field (on users as u | where u.age > 30). In lookup and union sub-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, including lookup and union. The rows can never be written.
  • as of and history are described in Past states and history, on view in 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:

StagesRule
union, pivot, windowAlways compound
lookup / joinCompound, except the one-line form whose header contains into
load, groupCompound when written without arguments; compact one-line form otherwise
every other stageAlways simple
on users
| lookup orders
    | where status == "paid"
    | into paid
| end
| where active
  • A missing | end is an error.
  • An end without 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 with get(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

LiteralExamplesNotes
Nullnull
Booleantrue, false
Integer0, 42, 18446744073709551615Signed 64-bit when it fits, otherwise unsigned 64-bit
Float9.5, .75, 1e3, 1.5e-10Finite 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

TokenMeaning
== != < <= > >=Comparisons
and, or, notBoolean operators
&&, ||, !Symbolic synonyms of and, or, not
+ - * / %Arithmetic
in, not inMembership 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)
$nameParameter or variable

Values and types

The value model

Documents are schema-less. Every field holds one of:

KindDescription
nullExplicit null
booleantrue / false
numberSigned integer (i64), unsigned integer (u64) or finite float (f64)
stringUTF-8 text
arrayOrdered values of any kind, including mixed kinds
objectNested 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.

  • insert and load generate it when absent.
  • load accepts 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.
  • select always keeps _id.

Comparison rules

  • Integers and floats compare numerically: age == 31.0 matches age: 31.
  • Strings compare lexicographically: name >= "B" and name < "D".
  • No cross-kind coercion. age == "31" is an error, not false. 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) is true or false and never fails.
  • coalesce(nickname, name) returns its first present, non-null argument.
  • and, or, if and case evaluate lazily, so exists(x) and x > 3 is safe.
  • A ? after a path segment reads a missing field as null: nick?, profile?.country. == and != accept null on 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:

  • near discards 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

FormExample
Literal42, "paid", true, null
Field pathage, profile.country
Optional pathnick?, profile?.country
Unarynot active, !active, -qty, +qty
Binaryage >= 18, price * qty
Group(city == "Paris" or city == "Lyon")
Membershipstatus in ["paid", "pending"], "dev" in tags, region not in ["US"]
Rangeage between 18 and 30, price not between 10 and 20
Array[qty, qty * 2, "x"]
Function callround(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.

LevelOperators
1or, ||
2and, &&
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

ConstructSemantics
ComparisonsNumbers compare across integer and float; strings lexicographically; any other cross-kind comparison is an error
and / or / notOperands must be booleans. and and or short-circuit
in / not inThe 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 zeroError. Use div(a, b, fallback) for a safe division
Unary -Integer negation when possible, otherwise float
TextNo + 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

FunctionResultExample
abs(x)Absolute value, integer keptabs(-3) → 3
sqrt(x)Square root (float); a negative input is an errorsqrt(16) → 4.0
pow(x, y)Power; integer when exactpow(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 roundtrunc(-2.7) → -2
floor(x), ceil(x)Rounded down / up, as an integerfloor(12.5) → 12
sign(x)-1, 0 or 1sign(-3) → -1
ln(x), log10(x), log(x, base), exp(x)Logarithms (positive input) and exponential, as floatslog(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 skippedsum(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 / bdiv(price, qty - 1, 0)

Logic, missing values and conversions

FunctionResultExample
if(cond, then, else)Only the chosen branch is evaluatedif(age >= 18, "adult", "minor")
case(c1, v1, c2, v2, ..., [default])Value of the first true condition, else default (or null); lazycase(qty > 5, "bulk", qty > 1, "few", "one")
coalesce(a, b, ...)First argument that is present and not nullcoalesce(nick, name)
exists(x)Whether x is present; never failsexists(nickname)
optional(x)x, or null when missing; what x? meansoptional(nick)
nullif(a, b)null when a == b, else anullif(status, "")
between(x, low, high)low <= x <= high; what x between low and high meansbetween(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 rangebucket(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 nullto_int("42") → 42
to_float(x)Float from a number, a numeric string or a booleanto_float("2.5") → 2.5
to_string(x)Text; numbers, booleans, arrays and objects are written as JSONto_string(3) → "3"
to_bool(x)From a boolean, a number (non-zero), or true/false/1/0/yes/noto_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.

FunctionResultExample
len(x)Characters of a string, items of an array, keys of an objectlen(tags) → 2
lower(s), upper(s), trim(s), ltrim(s), rtrim(s)Text transformsupper(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 arraysconcat(name, "@", city), concat(tags, ["new"])
substr(s, start [, length])Substringsubstr("Paris", 0, 3) → "Par"
split(s, separator)Array of parts; an empty separator splits characterssplit("a,b", ",") → ["a","b"]
replace(s, from, to)Replaces every occurrencereplace(city, " ", "-")
index_of(x, y)Position of y in a string or an array, -1 when absentindex_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 arrayscontains(name, "li"), contains(tags, "dev")
starts_with(s, p), ends_with(s, p)Prefix / suffix teststarts_with(city, "Pa")

Arrays

FunctionResultExample
at(array, i)Item i (negative from the end), null when out of rangeat(tags, -1) → "dev"
slice(array, start [, length])Sub-arrayslice(tags, 1)
reverse(x)Reversed array or stringreverse(tags)
sort_array(array [, "asc"|"desc"])Sorted copy; items must be comparablesort_array(scores, "desc")
unique(array)Distinct items, first occurrence keptunique([1, 1, 2]) → [1,2]
flatten(array)Flattens one levelflatten([[1], 2]) → [1,2]
range(start, end [, step])Integers from start up to (excluding) end; at most 1,000,000 itemsrange(0, 6, 2) → [0,2,4]

Objects

FunctionResultExample
keys(o), values(o)Keys / values of an objectkeys(profile) → ["country","score"]
get(o, key [, default])Value under any key, including keys that are not identifiersget(meta, "display name", "")
has(o, key)Whether the key is presenthas(profile, "score")
merge(o1, o2, ...)Shallow merge, later objects win; null arguments are skippedmerge(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.

FunctionResultExample
now()Current time, epoch milliseconds
parse_date(t)Epoch millisecondsparse_date("1970-01-02") → 86400000
date_trunc(t, unit)Start of the unit, epoch millisecondsdate_trunc(at, "month")
date_add(t, n, unit)Epoch milliseconds; adding months clamps the daydate_add("2026-01-31", 1, "month") → 2026-02-28
date_diff(start, end, unit)Unit boundaries crossed from start to enddate_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, milliseconddate_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 monthfiscal_year("2026-07-01", 6) → 2027
fiscal_month(t, closing)Month within the fiscal year, 1 to 12fiscal_month("2026-07-01", 6) → 1
fiscal_start(t, closing), fiscal_end(t, closing)First and last day of the fiscal year, epoch millisecondsfiscal_end("2026-07-01", 6) → 2027-06-30
month_end(t)Last day of the month, epoch millisecondsmonth_end("2024-02-10") → 2024-02-29
days_in(t, unit)Number of days of the unit holding tdays_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 $name outside 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 (for in), objects (for insert). A parameter can appear wherever its literal could: limit $n, near embedding, $vector.
  • An unbound $name fails 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 $name appears. 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 = qty is 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
FieldMeaning
accesscollection_scan or primary_key_lookup
executorstreaming, governed_blocking, transactional (writes), or rejected when the shape has no bounded executor
modeMemory contract of the plan
operatorsPhysical operators in order, with shorthand forms expanded
read_onlyWhether the query writes

Variables and parameters are bound first, so explain let $n = 2 | on orders | limit $n shows limit 2.


Stage reference

RoleStages
Filtering and projectionwhere, select / project, derive, rename, drop, root
Vector searchnear
Ordering and pagingsort, limit, skip / offset, first, single, sample
Arraysunwind
Deduplication and countingdistinct, count
Aggregationgroup, bucket, pivot
Ordered rowswindow, fill
Combining collectionslookup / join, union
Writesinsert, 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.
  • near does not reorder documents. Pair it with sort _distance and limit k for k-nearest-neighbours, or with where _distance < t for 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 null sort before every other value in ascending order, and after them in descending order.
  • Followed by limit N, sort runs 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 empty is given: the field is then null.
  • 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.

MeasureResultDefault alias
sum <field>Sum of the numeric values (integer while exact)<field>
avg <field>Mean of the numeric values, null when there are noneavg_<field>
min <field>, max <field>Smallest / largest non-null valuemin_<field>, max_<field>
first <field>, last <field>Value on the first / last row of the group, in input orderfirst_<field>, last_<field>
first <value> by <order>, last <value> by <order>Value on the row of smallest / largest <order>, whatever the input orderfirst_<field>, last_<field>
countNumber of rowscount
count <field>Number of rows where the field is present and not nullcount_<field>
list <field>Array of the values neither missing nor null, in input orderlist_<field>
median <value>, percentile <value> <fraction>, stddev <value>The functions of the same name over the group’s numbersmedian_<field>, percentile_<field>, stddev_<field>
  • A measured value, and a by <order>, may be any expression. An expression needs as <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, derive it 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 group spills 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.

  • values takes one or more measures, written like select items. 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: pivot does 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 by is required and appears once. partition by is 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.
FunctionValue on each rowDefault alias
lag <field> [<rows>]The field <rows> rows earlier in the partition (default 1); null before the firstlag_<field>
lead <field> [<rows>]The field <rows> rows later; null after the lastlead_<field>
cumsum <field>Running sum up to this row; null before the first numbercumsum_<field>
first <field>, last <field>The field on the first / last row of the partitionfirst_<field>, last_<field>
row_number1, 2, 3… within the partitionrow_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 by fields compare equal, or the whole input without by.
  • A new row holds the by fields of the partition, the grid value and the with values. Every other field is missing.
  • Rows already there pass unchanged. Rows come out sorted by the by fields, 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 as start is: "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>.field reads the joined document, <outer alias>.field reads 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
  • with sets the mode and is required exactly once. At least one chunk is required.
  • Each chunk is a JSON array of objects.
  • load must be the first stage.
  • Rows without an _id get a fresh one. Rows with an _id are matched against existing documents:
Mode_id exists_id absent from the collectionRow without _id
replacewhole document replacedinsertedinserted
mergeincoming fields merged over stored onesinsertedinserted
updateincoming fields merged over stored oneserror, nothing committederror

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, lookup and union collections included.
  • history returns 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.
FieldMeaning
_idIdentifier of the history row: distinct and ordered
_doc_idIdentifier of the document
_versionVersion of the document written, or deleted for a delete row
_opinsert, replace, delete (the row holds the document as it was deleted), or baseline (present when history recording started)
_generationCommit generation
_atCommit time (ISO 8601), null when not recorded
_by, _by_deviceIdentity 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 $name values.
  • 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
  • exec is the last clause. The body runs from exec to the two final end.
  • The parameters of a view are the $name its body reads without declaring them with let. They are bound by the reading query.
  • The body is checked when the view is defined, and refused when it writes.
  • Each define writes a new version and supersedes the previous one. History is never rewritten.
  • define view <name> | description "..." | end changes the description. define view <name> | version <n> | end restores version n as 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, glacierdb computes 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=off to 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:

  1. Pure streaming. Any mix of where, select, derive, rename, drop, root, near, unwind, skip, limit, first, single, optionally followed by count. Documents flow one by one, and a row bound (limit, first) stops the scan early.
  2. Streaming prefix, then blocking stages. The blocking stages are sort, distinct, group, pivot, window, fill, sample, lookup and union. Any number of them may follow one another, each followed by row-local stages.

In the second shape:

  • a skip / limit (or first) after a blocking stage bounds that stage: sort k | skip 10 | limit 5 runs as a bounded Top-15;
  • sort and group spill 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.

CollectionContent
_memoryOne global row (profile, limits, current and peak bytes, pressure state, spill counters) and one row per memory class
_query_memoryQuery admission budget and one row per running operation
_memory_eventsRecent memory reservations and releases
_index_observationsPer-query-shape execution statistics: fingerprint, access path, executions, scanned, returned, elapsed
_viewsViews and their versions
_cacheKept 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}}}
FieldMeaning
scanned / filtered / returnedDocuments read, rejected by predicates, emitted
inserted / replaced / deleted / committedWrite counters, and whether a transaction was committed
strategiesPhysical strategies used: collection_scan, primary_key_lookup, …
streamedtrue when rows were streamed to the client as they were produced
timings_usPer-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.

LayerWhenExample
LexerUnknown character, bad string or numberlex error: unexpected character `@` at 9..10
ParserPipeline structureparse error: expected stage name, found operator at 11..12
PlannerStage arguments, expressions, orderingunknown stage "frobnicate", unknown function `nope`
ExecutionData-dependentfield 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

AreaLimitationWorkaround
Missing fieldsA missing field fails the query, except in exists, coalesce and optional pathsx?, exists(x) and ..., coalesce(x, default)
group keysField paths onlyderive the computed key first
sortThe values of a key other than null must all be numbers, all texts or all booleansto_string(...) or to_float(...) in a derive first
pivotAggregate cells must fit the query memory budget (no spill)Filter or select before it
windowTop level only; each partition must fit the query memory budget; keys are field pathsPartition more finely; derive computed keys first
fillTop level only; <field> and by are field paths; at most 100,000 grid valuesderive computed fields first
DatesNo native type; date functions read epoch milliseconds or ISO-8601 strings, in UTCdate_* functions
Vector searchExact distance computation, no approximate indexFilter candidates with where before near
Not in the languageRecursive 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:

VariablePurpose
OGD_BINDMain daemon bind address
OGD_LOCAL_BINDOptional local bind address
OGD_STORAGEmemory or glacier
OGD_STORAGE_PATHGlacier store path
OGD_MEMORY_LIMITProcess memory limit
OGD_READ_TIMEOUT_MSRead timeout
OGD_WRITE_TIMEOUT_MSWrite timeout
OGD_NODE_CAPABILITIESEnabled service capabilities
OGD_NODE_ROLEmaster or node
OGD_NODE_IDENTITYNode identity
OGD_NODE_IDENTITY_FILENode identity file
OGD_NODE_IDENTITY_PASSWORDPassword for the node identity
OGD_BACKUP_PATHBackup directory
OGD_AUTH_REQUIREDEnable authorization enforcement
OGD_CLASSIC_AUTH_ENABLEDEnable classic authentication
OGD_HEARTBEAT_ENABLEDEnable heartbeat
OGD_HEARTBEAT_INTERVAL_MSHeartbeat interval
OGD_DEBUG_QUERYQuery debugging
OGD_IMPORT_METRICSImport 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.

glacierdbopenglacier
Primary positioningDatabaseResource / database engine ecosystem
Database engineopenglacier-coreopenglacier-core
Daemonglacierdbogd
CLIglaciercliogcli
Default storageGlacierMemory
AuthenticationEnabledEnabled
EventsEnabledEnabled
TUIIncludedAvailable through Core features
Environment variablesOGD_*OGD_*
Core engineSameSame

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:

  1. A query caching engine. At the origin, openglacier was only that: a layer keeping the results of queries.
  2. A small transactional engine, built on redb, an embedded key-value store written in pure Rust. A project I like very much.
  3. 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

antonroots

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

ConceptExists independently of openglacierRepresented in the WorkspaceProvides capabilities
Resource✅ YesThrough an Object✅ Yes
Connector❌ No❌ No❌ No
Object❌ No (representation)✅ YesReflects 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.