hraness

a policy language for organisms, not authorization

what to leave out when programs live inside a simulation

Drafted by an AI agent at Ben Guo's direct request from the Hraness source repositories, and checked against those sources before publication.

Most policy languages answer a question about permission: may this principal perform this action on this resource. Cedar, Rego, and XACML all live in that family. The policy language inside Platonik answers a different question entirely: what should this organism do next, given only what it can sense, in a world that keeps running whether or not anyone is watching.

The distinction matters because the failure modes are different. An authorization policy is evaluated occasionally, by a service that controls when. An organism policy is evaluated every simulated tick, inside a deterministic world, under a hard fuel budget, by an interpreter that must charge for every step and replay the whole run exactly on demand. When Platonik’s documentation says “a submitted organism must function when the scientist is disconnected,” that is a language-design requirement, not a narrative flourish. The external agent that wrote the program, an LLM acting as the scientist, is not available at runtime. The program is on its own.

a language that is mostly refusals

The language is defined in crates/platonik-core/src/model.rs, and its grammar is short enough to quote in full:

pub struct Program {
    pub rules: Vec<Rule>,
}
pub struct Rule {
    pub when: Vec<Condition>,
    pub action: Action,
    pub remember: Option<MemoryWrite>,
}

Execution is a first-matching-rule scan. On each activation the interpreter walks rules in order, evaluates every condition in a rule’s when list, and fires the first rule whose conditions all hold. If no rule matches, the cell performs Wait. There is no jump, no call, no loop. One activation is one linear pass through a list: at most 32 rules, each with at most 8 conditions. That is the entire control-flow story.

This is the first thing to notice about the design: it is a language of refusals. The interesting work is done by what the grammar cannot express. There is no recursion, because a program that can call itself can run forever. There are no unbounded integers, because a u8 register cannot hide an accumulator that grows with the input. There is no heap allocation at all; the cell’s entire writable state is declared up front. The list of forbidden constructs in docs/engine.md reads like a threat model: no unchecked native code, no unbounded integers, no recursion, no unbounded allocation, and no access to the network, filesystem, wall clock, or the evaluator that runs it.

the actual grammar

Conditions and actions are closed enums, so the whole instruction set can be tabulated. The thirteen conditions in model.rs are carrying, at_source, at_depot, at_beacon, at_receiver, blocked, has_message, message_bit, memory, heading, has_material, assembly_stage, and assembly_edits. The thirteen actions are move, turn, pickup, drop, wait, write_memory, take_message, send, route, gather_material, build, activate, and edit_direction. The last six exist only under newer protocol versions (gather_material, build, and activate arrive with platonik-habitat-v3, and edit_direction with v4), and validate_program in crates/platonik-core/src/sim.rs rejects them outright in older experiments.

A working program looks like this. resilient_courier in crates/platonik-core/src/fixtures.rs is the public witness program the challenge generator uses to prove its cases solvable; rendered as the typed JSON the CLI actually accepts, its movement core is:

{
  "rules": [
    { "when": [
        { "kind": "carrying", "value": false },
        { "kind": "at_source", "value": true } ],
      "action": { "kind": "pickup" } },
    { "when": [
        { "kind": "carrying", "value": true },
        { "kind": "at_receiver", "value": true } ],
      "action": { "kind": "drop" } },
    { "when": [
        { "kind": "blocked", "direction": "right", "value": false } ],
      "action": { "kind": "move", "direction": "right" } },
    { "when": [
        { "kind": "blocked", "direction": "forward", "value": false } ],
      "action": { "kind": "move", "direction": "forward" } }
  ]
}

Read top to bottom: pick up a spark when empty at a source, drop it when carrying at a receiver, otherwise move in the first unblocked direction from a fixed preference order: right, then forward, then left, then back. That is a right-wall follower in eleven lines. The real fixture adds the remaining directions, and the whole program is still small enough to audit by eye, which is the point. A language where a complete useful organism fits on a page is a language where “what will this do?” is a checkable question rather than a research program.

The optional remember field deserves a second look because it is the language’s one piece of syntactic sugar. A rule with "remember": {"slot": 2, "value": 1} writes that byte to a register when the rule fires, in the same activation as its action. In policy.rs this is implemented literally as a charged MemoryWrites unit after the action executes, so the sugar doesn’t sneak computation past the meter. It exists because “see a message, record that you saw it, and forward it” is the single most common organism pattern, and making it one rule instead of two keeps programs readable without changing the cost model.

what a cell can see

A language is its observables as much as its syntax. A Platonik cell carries, per CellState in model.rs: a position and heading, memory: [u8; 4] (four byte registers, no more), evidence: [Option<u32>; 4] recording which physical spark a register’s bit came from, cargo: Option<Spark> holding at most one unit, inbox: [Option<Signal>; 4] with four message ports, and an optional material token under construction versions. That is the complete machine state of an organism.

Everything a condition can test is on that list or one step away from it. A cell can ask whether it is carrying, whether it stands on a source, depot, beacon, or receiver, whether a relative direction is blocked, whether a message waits on a port and what its bit is, what its own registers hold, which way it faces, and (under v3 and v4) whether it holds material and what stage an adjacent declared assembly has reached. It cannot read another cell’s registers, a remote map, the global clock, the seed, the fixture’s name, or its own identifier. Identity and ancestry are visible to the observer running the experiment; they are deliberately unavailable to the program. The organism’s epistemology is local in the strong sense: the sensors are the physics.

Signals arrive over declared Link values with integer delays of 1–16 ticks between adjacent endpoints, land in one of the four inbox ports, and expire if unread. Inbox observations are transient, and only an explicit take_message into a byte register preserves a value across ticks. Communication is therefore also a memory decision: you can hold four bits of history, and choosing which four is the programming.

what was left out, and why

Each refusal does a specific job in the engine’s accounting:

  • No loops or recursion means one activation is one bounded linear scan, so the engine can cap per-activation work at a fixed interval (1–1,024 units under v1–v3, up to 16,384 under v4) and mean it. Termination is structural, not a property to be proved.
  • u8 registers and closed enums mean state space stays enumerable. The repository exploits this directly: the field-expedition evaluation checked all 2,048 local movement-and-closure configurations of a small region against an independent coordinate rule. Exhaustive verification is affordable precisely because the language keeps the space small.
  • No allocation means memory use is a declared constant, charged at load time as canonical bytes, and cannot grow mid-run to escape the ledger.
  • No native code, filesystem, network, clock, or evaluator access means the interpreter is the whole machine. There is no side channel by which a program could observe the harness, burn unmeasured work, or smuggle in the answer key. The generator’s own test cases cannot be read by the program being tested.

The omissions also serve a subtler purpose. Because every legal program is a small typed value with canonical serialization, a program is data in a strong sense: it hashes deterministically, diffs cleanly, embeds in a blueprint for in-world construction, and survives as evidence in a committed receipt. The edit_direction action in v4 builds an entire variation mechanic on this: a builder cell rewrites a single Move or Turn operand inside a copied body, and the result is re-validated and re-hashed as an ordinary program. That trick is only possible because “program” and “bounded JSON value” are the same thing.

policy means decisions

It is fair to ask why this counts as a policy language at all rather than an instruction set or a rules engine: the abstraction is a decision procedure over local state, not a computation. The program doesn’t transform inputs into outputs in the functional sense; it maps “what is true around me right now” to “what I do this tick.” Rules are prioritized contingency plans, and rule order is the entire semantics of conflict resolution: the first match wins, so programmers write exceptions before defaults, exactly as in a firewall ruleset or a classic production system.

The lesson generalizes past the game. When programs must run unattended inside a host system (agents in a simulation, extensions in a wallet, jobs in a sandboxed runner), the language design question is not “what should be expressible” but “what must be impossible, and how do we charge for the rest.” Platonik’s answer is to make impossibility syntactic: the dangerous things are not rejected by a linter, they are absent from the grammar, so no input can spell them. Everything that remains is small, priced, replayable, and inspectable. A policy you can enumerate is a policy you can trust to run while nobody is home.

sources

  • platonik: the public repository; crates/platonik-core/src/model.rs defines Program, Rule, Condition, Action, and CellState; crates/platonik-core/src/policy.rs implements the first-match interpreter; crates/platonik-core/src/fixtures.rs holds resilient_courier
  • docs/engine.md: the bounded-rules contract and the forbidden-construct list
  • docs/rust-bridge.md: the implemented prototype’s laws and limits
  • docs/challenges.md: the witness program and the public challenge families