holo-program

Programs as items, membranes as regions, keys as gates.

A memory substrate with three stacked layers under one interface. Programs are stored as key-value items and executed by unbind-then-classify. Membranes are nested regions with child-to-parent inheritance and P-systems operations. Cryptographic keying derives item vectors from a secret; without the key, the trace is noise. Everything is addressable through a single ask() primitive.

The three layers are views of one vector space. The caller does not specify which layer to search. ask() returns heterogeneous hits.

What it does

from holo_program import HoloProgram

prog = HoloProgram(d=2048, key=None)

# Programs as items
prog.add_op("root", "inc", "0", "1")
prog.add_op("root", "inc", "1", "2")
prog.add_op("root", "double", "2", "4")

# Execute a chain
result = prog.run_chain("root", ["inc", "inc", "double"], "0")
# result = {"path": ["0", "1", "2", "4"], "ok": True}

# Membranes with inheritance
prog.add_membrane("alice", parent="root")
prog.store("alice", "alice_lives_in_paris")
prog.query("alice", "alice_lives_in_paris")   # ~1.0
prog.query("root",  "alice_lives_in_paris")   # ~0.0

# Keyed access
p_secret = HoloProgram(key="secret")
p_secret.store("root", "classified")
p_wrong = HoloProgram(key="wrong")
p_wrong.membranes["root"].trace = p_secret.membranes["root"].trace.copy()
p_secret.query("root", "classified")   # ~1.0
p_wrong.query("root", "classified")    # ~0.0

# Non-flat query
hits = prog.ask("inc")
# [operation] membrane=root  role=op  inc(0) -> 1
# [operation] membrane=root  role=op  inc(1) -> 2

Why three layers

Standard memory tools are flat. A vector database accepts an embedding; a KV cache accepts a key; a graph database accepts a pattern. Each tool has one interface and one data layer. The user adapts the problem to the tool.

The substrate here stacks three layers over the same vector space. Each layer has been built before. The composition is what is new. A user stores a fact, defines an operation, puts it in a membrane, and locks it behind a key β€” all through the same primitives, all retrievable through the same ask() call.

Installation

pip install numpy

No other dependencies. Single file, approximately 600 lines.

Usage

CLI

python holo_program.py
python holo_program.py --output results/

Runs eight demonstrations and writes a JSON state file.

Python

from holo_program import HoloProgram

prog = HoloProgram(d=2048, key=None, threshold=0.05)

# Membranes
prog.add_membrane("alice", parent="root")
prog.add_membrane("alice_kitchen", parent="alice")

# Storage
prog.store("root", "earth_round")
prog.store("alice", "alice_lives_in_paris")
prog.store("alice_kitchen", "manager_is_bob")

# Operations
prog.add_op("root", "serve", "customer", "coffee")
prog.add_op("alice_kitchen", "brew", "coffee", "ready")

# Execute
result = prog.run_chain("root", ["inc", "inc"], "5")
result = prog.apply("alice_kitchen", "brew", "coffee")  # "ready"

# Data-driven walk
walk = prog.run_data_driven("root", "cold")  # ["cold", "warm", ...]

# P-systems operations
prog.move("private_note", "alice", "root")
prog.dissolve("alice_kitchen", "alice")

# Non-flat query
hits = prog.ask("coffee", top_k=8)

# Cryptographic layer
commitment = prog.commit("root", "secret_value", salt="random_salt_123")
ok = prog.verify("root", "secret_value", "random_salt_123", commitment)

Results

All results at D=2048, threshold=0.05. Self-test verifies four primitives before any demonstration: bind/unbind identity, key separation, projection recovery, and random-key key-value retrieval (49/50 at 50 superposed pairs).

Program execution

Twenty inc operations (i β†’ i+1 for i = 0..19), twenty double operations (i β†’ 2i), ten square operations (i β†’ iΒ²) stored as key-value pairs.

Chain Start Path Result
inc, inc, double 3 3 β†’ 4 β†’ 5 β†’ 10 correct
square, inc 4 4 β†’ 16 β†’ 17 correct
double, inc, inc 5 5 β†’ 10 β†’ 11 β†’ 12 correct
inc, square 2 2 β†’ 3 β†’ 9 correct

Single applies: inc(7) = 8, double(7) = 14, square(7) = 49.

The chains compose across different operations. The substrate does not know which operations exist; it reads the key, retrieves the value, classifies the out, and passes the result forward.

Data-driven execution

State transitions: cold β†’ warm β†’ hot β†’ boiling β†’ evaporated.

Starting at cold with no predefined chain, the substrate walks by finding whichever operation matches the current value at each step:

cold -> warm -> hot -> boiling -> evaporated
steps: 4

Starting at evaporated (no matching operation), the walk terminates immediately.

Membrane inheritance

Three nested membranes: root, alice, alice_kitchen.

Query root alice alice_kitchen
earth_round +1.028 +1.028 +1.028
alice_lives_in_paris +0.023 +1.004 +1.004
kitchen_floor_5 -0.026 -0.026 +1.020
manager_is_bob -0.004 -0.004 +1.020

The child sees the parent's facts. The parent does not see the child's. Inheritance is one-way, from child to parent.

Movement and dissolution

Move. private_note stored in inner:

root inner
before +0.027 +1.000
after +1.027 +1.027

The content transferred completely.

Dissolution. middle_fact in middle, then dissolve middle into root:

root middle
before +0.009 +1.000
after +1.009 (gone)

Content transferred, membrane removed.

Cryptographic keying

Two programs share the same trace. One has the correct key; the other has a wrong key.

Candidate Correct key Wrong key
classified_meeting_place +0.997 +0.004
classified_meeting_time +0.997 -0.007
paris -0.018 -0.022
3pm -0.028 +0.034
unrelated_word -0.028 +0.010

With the correct key, stored items read at +0.997 and non-stored at near zero. With the wrong key, all items read near zero. The trace carries no usable signal without the key.

Commitment scheme

Commitment = bind(vector(label), vector(salt)). Verification:

Correct (label, salt) Wrong salt Wrong label
True False False

Commitment vector magnitude is exactly 1.0000 (phase-only binding preserves unit modulus).

Unified ask()

Five example queries, each returning heterogeneous hits:

Query Result
alice [membrane] alice
alice_lives_in_paris [item] alice sim=+1.004
coffee [operation] serve(customer) β†’ coffee in root; [operation] brew(coffee) β†’ ready in alice_kitchen
alice_kitchen [membrane] alice_kitchen
manager_is_bob [item] alice_kitchen sim=+1.020

The caller does not specify which layer to search. The substrate reports what it has.

Programs as items

Querying ask("inc") returns the two stored inc operations.

Querying ask("3") returns both the operation where 3 is an output (inc(2) β†’ 3) and the operation where 3 is an input (double(3) β†’ 6). The role field distinguishes them.

The same primitive reads items and operations. There is no separate "program layer" from the caller's perspective.

API reference

HoloProgram

HoloProgram(d=2048, key=None, threshold=0.05)

Membrane management

  • add_membrane(name, parent="root") β€” create a nested membrane.

Storage

  • store(membrane, label, weight=1.0) β€” store an item.
  • add_op(membrane, op_name, in_val, out_val) β€” store an operation.
  • move(label, src, dst) β€” transfer an item between membranes.
  • dissolve(child, into) β€” merge a child membrane into its parent.

Execution

  • apply(membrane, op_name, in_val) -> out_val | None β€” single step.
  • run_chain(membrane, ops, start) -> dict β€” fixed sequence.
  • run_data_driven(membrane, start, max_steps=20) -> dict β€” follow whichever operation matches at each step.

Query

  • query(membrane, label) -> float β€” similarity in the membrane's inheritance chain.
  • ask(query, top_k=8) -> list[dict] β€” heterogeneous search across all membranes and all layers.

Cryptographic

  • commit(membrane, label, salt) -> np.ndarray β€” generate commitment.
  • verify(membrane, label, salt, commitment) -> bool β€” verify.

Diagnostics

  • stats() -> dict β€” per-membrane counts and magnitudes.
  • save_json(path) β€” full state.

Design notes

Random keys, not structured keys

Operations use fully random keys derived from a hash of (op_name, in_val), not from a structured binding of role vectors. An earlier version used norm(bind(op, op_name) + bind(in, in_val)), which produced correlated keys for operations sharing a component. With twenty inc operations, the shared inc component made the keys correlated and cross-talk swamped the signal; only single applies worked.

The random-key variant removes the cross-talk at the cost of treating every (op, in) pair as structurally distinct. The op role is still used for the value (bind(out, out_val)), but not for the key.

Parent direction

Inheritance goes from child to parent. A child sees its parent's facts; a parent does not see its child's facts. This is the natural direction for a "context" relation: a specific context inherits from a general one, not the other way around.

Keyed codebook

Item vectors are derived from random_vector(key, label). Different keys produce uncorrelated vectors for the same label. A trace built with key A, queried with key B, produces similarity near zero because the two codebooks are independent.

This is access control at the codebook level. Without the key, the trace is a random superposition of unknown vectors.

Limitations

Programs are not Turing-complete. Chains and data-driven walks compose operations. Branching, iteration, and recursion must be encoded as larger operation sets or implemented outside the substrate.

No catalysis. An operation cannot require a third object to be present. The structure supports it (a catalyst could be a role in the operation and a check against the local trace) but it is not implemented.

No priority. Multiple applicable operations are returned by find_applicable sorted by similarity. There is no specificity-based ordering or explicit precedence.

No timed rules. Operations have no validity windows.

No provenance. The substrate does not track which call produced which weight. Two sources that store the same fact look identical.

Keys are not secret against an adversary with the codebook. The key separation prevents a wrong-key query from retrieving items. It does not prevent brute-force matching against the trace if the label space is small.

Commitments are not cryptographic. bind(vector(label), vector(salt)) is a hash-like operation in the substrate's algebra. It is not a cryptographically secure commitment in the formal sense. It is useful for cooperative verification, not for adversarial security.

No cross-membrane queries. A query goes to one membrane. ask() searches all membranes for the query string, but it does not propagate the query through the inheritance chain.

No consolidation. Traces grow linearly. Long-running stores need external management. The four-axis tool provides consolidation; it is not integrated here.

Citation

@misc{holo-program2026,
  title  = {holo-program: Programs as items, membranes as regions,
            keys as gates},
  author = {zeechimp},
  year   = {2026},
  note   = {A memory substrate with three stacked layers under a
            single non-flat interface.}
}

References

  • Plate, T. A. "Holographic Reduced Representations." IEEE Transactions on Neural Networks 6:3 (1995), 623–641.
  • Kanerva, P. "Hyperdimensional Computing." Cognitive Computation 1:2 (2009), 139–159.
  • PΔƒun, Gh. "Computing with Membranes." Journal of Computer and System Sciences 61:1 (2000), 108–143.
  • Gayler, R. W. "Vector Symbolic Architectures Answer Jackendoff's Challenges." ICCS/ASCS (2003).

License

Apache 2.0

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support