Koinii adjudication — the DEFAULT policy: leave-open-and-notify, plus the
lifecycle that keeps disputes from piling up, plus arbiter escalation. A thin CLIENT
wrapper over the dispute reads (vaelii.impl.koinii.dispute) — it touches no
engine internals and changes belief only through ordinary asserts / retracts.
koinii's honest first answer to a disagreement is NOT to pick a winner. When two agents
assert P and ¬P at :default, the KB stays paraconsistent — both coexist, argue
reports :contradiction (Priest's LP) — and this layer just records the dispute open,
pushes it to whoever is watching, and manages its life. Automatic resolution by source
trust is a harder, engine-side policy; do not reach for it here.
Three policies, one default (koinii.md, Adjudication: split by policy):
:monotonic assertion of the upheld side; its strength defeats the losing
:default side, so the clash clears, why explains who ruled, and retracting the
ruling reopens the dispute (cascading).The dispute module owns the reads, the state vocabulary, and the dispute id; THIS module owns the policy
the dispute module deliberately left out — the clock, the timeout, and the notify sinks. The
clock is the engine clock (v/*clock*), so a lifecycle stamp and its assertion's
:created provenance agree.
Additive, like the sibling koinii modules: only the public core API plus koinii
dispute and identity — nothing in core loads it.
Koinii adjudication — the DEFAULT policy: leave-open-and-notify, plus the lifecycle that keeps disputes from piling up, plus arbiter escalation. A thin CLIENT wrapper over the dispute reads (`vaelii.impl.koinii.dispute`) — it touches no engine internals and changes belief only through ordinary asserts / retracts. koinii's honest first answer to a disagreement is NOT to pick a winner. When two agents assert P and ¬P at `:default`, the KB stays **paraconsistent** — both coexist, `argue` reports `:contradiction` (Priest's LP) — and this layer just records the dispute open, pushes it to whoever is watching, and manages its life. Automatic resolution by source trust is a harder, engine-side policy; do not reach for it here. Three policies, one default (koinii.md, *Adjudication: split by policy*): - **Leave-open-and-notify** *(the default, here)* — record open, notify, change no belief. Correct for a ground truth people curate. - **Arbiter escalation** *(also here, client)* — a designated arbiter's ruling is an ordinary `:monotonic` assertion of the upheld side; its strength defeats the losing `:default` side, so the clash clears, `why` explains who ruled, and retracting the ruling reopens the dispute (cascading). - **Trust-resolve** — out of scope; that is engine work, not this layer's. The dispute module owns the reads, the state vocabulary, and the dispute id; THIS module owns the policy the dispute module deliberately left out — the **clock**, the **timeout**, and the **notify sinks**. The clock is the engine clock (`v/*clock*`), so a lifecycle stamp and its assertion's `:created` provenance agree. Additive, like the sibling koinii modules: only the public core API plus koinii `dispute` and `identity` — nothing in core loads it.
Koinii belief projection and own-statement disregard — reasoning about what agents
hold, built on modal belief projection (vaelii.impl.modal, docs/belief.md) and
except visibility masking.
Two capabilities, and a boundary between them that matters:
Projection — read what an agent holds. (believes agent P) proves P in the
agent's OWN context (CxAgent<agent>), never the asker's, so agents may
hold contradictory beliefs without the KB contradicting itself, and asking what one
agent believes never pulls in another's. believe-own links an agent's belief
context to its koinii write context, so it believes what it asserted and endorsed.
This is the whole cross-agent story: you ask what an agent holds from that agent's
own context — you never merge one agent's beliefs into another (a cross-agent
genlCx would, and would drag one agent's contradictions into the other).
Disregard — an agent reversibly withdraws its OWN statement. disregard puts
an (except (sentexHandle H)) in the agent's own context, hiding H for reads and
derivations, reversibly (restore!), without deleting it. It is restricted to the
agent's own statements by construction, and that restriction is the point:
except is an index-layer mask — it removes a sentex from view — so using it
across agents (agent B hiding agent A's claim) would make a common-descendant context
unable to argue: argumentation needs both a claim and its rebuttal visible so the
TMS can weigh them, and an index-layer removal takes the claim out of view entirely.
Cross-agent disagreement is therefore dispute / argue (speech-acts + adjudication), which
keeps both sides visible; except is only ever an agent editing the visibility of
what it itself said.
Additive, like the other koinii modules: only the public core API plus modal,
sentex, and koinii identity — nothing in core loads it.
Koinii belief projection and own-statement disregard — reasoning about what agents hold, built on modal belief projection (`vaelii.impl.modal`, `docs/belief.md`) and `except` visibility masking. Two capabilities, and a boundary between them that matters: - **Projection — read what an agent holds.** `(believes agent P)` proves `P` in the agent's OWN context (`CxAgent<agent>`), never the asker's, so agents may hold contradictory beliefs without the KB contradicting itself, and asking what one agent believes never pulls in another's. `believe-own` links an agent's belief context to its koinii write context, so it believes what it asserted and endorsed. This is the whole cross-agent story: you ask what an agent holds *from that agent's own context* — you never merge one agent's beliefs into another (a cross-agent `genlCx` would, and would drag one agent's contradictions into the other). - **Disregard — an agent reversibly withdraws its OWN statement.** `disregard` puts an `(except (sentexHandle H))` in the agent's own context, hiding `H` for reads and derivations, reversibly (`restore!`), without deleting it. It is restricted to the agent's own statements **by construction**, and that restriction is the point: `except` is an **index-layer** mask — it removes a sentex from *view* — so using it across agents (agent B hiding agent A's claim) would make a common-descendant context unable to *argue*: argumentation needs both a claim and its rebuttal visible so the TMS can weigh them, and an index-layer removal takes the claim out of view entirely. Cross-agent disagreement is therefore `dispute` / argue (speech-acts + adjudication), which keeps both sides visible; `except` is only ever an agent editing the visibility of what it *itself* said. Additive, like the other koinii modules: only the public core API plus `modal`, `sentex`, and koinii `identity` — nothing in core loads it.
Koinii catch-up: make 'an agent that was offline catches up on what it missed' CORRECT, including the case the naive version gets wrong — the feed's ring is bounded (256 events), so an agent gone long enough is lagged PAST recovery and its stored cursor can no longer replay the gap.
This is exactly the CDC snapshot+tail pattern (Debezium / Kafka): a consumer that
joined late — or fell too far behind — re-reads current state (the snapshot), then
resumes streaming from the newest offset (the tail). Koinii's context re-read IS the
snapshot half. The subscribe loop (the channel) handles the happy path; this handles
the gap. Commits koinii.md's D6 (snapshot+tail) and D7 (order).
Why the snapshot is authoritative, not a fallback nicety. The change feed is add-oriented: it reports a datum ENTERING or a derived conclusion LEAVING belief, but a premise RETRACTED is dropped — its record is gone and 'a datum the dependency-directed sweep deleted is dropped rather than guessed at' (docs/feed.md). So the incremental stream cannot, by itself, be a complete replica: only a full re-read reflects retractions. The tail is an optimization for the common case (koinii accretes — claims, replies, votes); the snapshot is the source of truth, and every catch-up path ends reconciled against it or against a live tail.
Snapshot reads through the CONE. A channel sees its agents' own-context sentexes up
the genlCx cone; sentexes-matching does NOT walk the cone (it scopes to a context's
own sentexes) but query does — so the snapshot is channel/query, whose solution set is
the same view the standing-query feed delivers.
Wire-only: the ring, the cursor, and lag exist on the wire feed (vaelii.impl.subscribe).
An in-process medium has no ring to fall off, so -feed-open/-feed-poll throw there and
a single-process agent needs none of this.
Additive: requires only channel and clojure.walk. Nothing in core loads it.
Koinii catch-up: make 'an agent that was offline catches up on what it missed' CORRECT, including the case the naive version gets wrong — the feed's ring is bounded (256 events), so an agent gone long enough is lagged PAST recovery and its stored cursor can no longer replay the gap. This is exactly the **CDC snapshot+tail** pattern (Debezium / Kafka): a consumer that joined late — or fell too far behind — re-reads current state (the **snapshot**), then resumes streaming from the newest offset (the **tail**). Koinii's context re-read IS the snapshot half. The subscribe loop (the `channel`) handles the happy path; this handles the gap. Commits `koinii.md`'s D6 (snapshot+tail) and D7 (order). **Why the snapshot is authoritative, not a fallback nicety.** The change feed is add-oriented: it reports a datum ENTERING or a derived conclusion LEAVING belief, but a premise RETRACTED is dropped — its record is gone and 'a datum the dependency-directed sweep deleted is dropped rather than guessed at' (docs/feed.md). So the incremental stream cannot, by itself, be a complete replica: only a full re-read reflects retractions. The tail is an optimization for the common case (koinii accretes — claims, replies, votes); the snapshot is the source of truth, and every catch-up path ends reconciled against it or against a live tail. **Snapshot reads through the CONE.** A channel sees its agents' own-context sentexes up the `genlCx` cone; `sentexes-matching` does NOT walk the cone (it scopes to a context's own sentexes) but `query` does — so the snapshot is `channel/query`, whose solution set is the same view the standing-query feed delivers. Wire-only: the ring, the cursor, and lag exist on the wire feed (`vaelii.impl.subscribe`). An in-process medium has no ring to fall off, so `-feed-open`/`-feed-poll` throw there and a single-process agent needs none of this. Additive: requires only `channel` and `clojure.walk`. Nothing in core loads it.
Koinii's core coordination library: the async assert / reply loop an agent runs over a shared channel, with the KB as the medium. An agent JOINS its own context, SUBSCRIBES to the channel over the change feed, and REPLIES to what it sees — all decoupled in time, nothing requiring two agents online at once.
The mechanism is already in the engine (the change feed, docs/feed.md); this layer is
ergonomics and correctness over it, not a new transport. It commits three pieces of
koinii.md's deployment shape to code:
CxAtlas),
lifted under the channel ((genlCx CxDeploy CxAtlas)) so the channel sees the union
of every agent's assertions. Identity fixes the write destination (identity).speech_acts'
answers / disputes / endorses / justifies), so it lives ON the target rather
than merely naming it: retract the target and its replies are torn down with it
(targetFollowingPredicate), no dangling edges.*writer-order* below.Two deployment shapes, one surface. The Medium protocol has two
implementations, and which one an agent joins is the whole single-process /
cross-process decision (koinii.md, When to stop):
wire — a daemon connection (vaelii.client). The inter-agent case: agents are
separate processes funnelling every write through the one daemon (the single writer),
and subscribe runs the feed's poll loop OFF the agent's own thread. This is the
mandatory shape the moment agents are separate processes, and the reason is the
writer's thread: an in-process feed callback runs ON the single writer's thread
(docs/feed.md), so one slow agent would stall the writer for everyone. Polling off
the agent's thread is what removes that coupling.local — an in-process KB. Simpler, and correct when every agent lives in one
process: subscribe is a plain core/watch listener, no cursor apparatus. The
callback runs on the writing thread, so a slow one still slows the writer — fine
single-process, wrong across agents, which is why wire exists.reply / assert / reply-many and the recovery reads are shape-agnostic — they run
the same over either medium; only subscribe differs, because only the feed does.
Additive, like the sibling koinii modules: the public core API, vaelii.client,
sentex, and koinii identity. Every write goes through the provenance-stamping
assert path — never bulk-assert-facts!.
Koinii's core coordination library: the async assert / reply loop an agent runs over a shared channel, with the KB as the medium. An agent JOINS its own context, SUBSCRIBES to the channel over the change feed, and REPLIES to what it sees — all decoupled in time, nothing requiring two agents online at once. The mechanism is already in the engine (the change feed, docs/feed.md); this layer is ergonomics and correctness over it, not a new transport. It commits three pieces of `koinii.md`'s *deployment shape* to code: - **D8, the per-agent context.** An agent writes only its OWN context (`CxAtlas`), lifted under the channel (`(genlCx CxDeploy CxAtlas)`) so the channel sees the union of every agent's assertions. Identity fixes the write destination (`identity`). - **D1, reply-as-meta-sentex.** A reply is a META-SENTEX on its target (`speech_acts`' `answers` / `disputes` / `endorses` / `justifies`), so it lives ON the target rather than merely naming it: retract the target and its replies are torn down with it (`targetFollowingPredicate`), no dangling edges. - **D7, the single-writer total order.** See the docstring on `*writer-order*` below. **Two deployment shapes, one surface.** The `Medium` protocol has two implementations, and which one an agent joins is the whole single-process / cross-process decision (`koinii.md`, *When to stop*): - **`wire`** — a daemon connection (`vaelii.client`). The inter-agent case: agents are separate processes funnelling every write through the one daemon (the single writer), and `subscribe` runs the feed's `poll` loop OFF the agent's own thread. This is the mandatory shape the moment agents are separate processes, and the reason is the writer's thread: an in-process feed callback runs ON the single writer's thread (docs/feed.md), so one slow agent would stall the writer for everyone. Polling off the agent's thread is what removes that coupling. - **`local`** — an in-process KB. Simpler, and correct when every agent lives in one process: `subscribe` is a plain `core/watch` listener, no cursor apparatus. The callback runs on the writing thread, so a slow one still slows the writer — fine single-process, wrong across agents, which is why `wire` exists. `reply` / `assert` / `reply-many` and the recovery reads are shape-agnostic — they run the same over either medium; only `subscribe` differs, because only the feed does. Additive, like the sibling koinii modules: the public core API, `vaelii.client`, `sentex`, and koinii `identity`. Every write goes through the provenance-stamping `assert` path — never `bulk-assert-facts!`.
Koinii cross-seat dereference: the DISTRIBUTED topology, where independent seats — separate processes, each holding its own copy of the KB — stay in sync by content-addressed commits rather than by sharing one live daemon. A seat asserts a sentence and commits; another seat pulls the same commit and resolves the same sentence from its own KB. The locator travels over a transport; the proof comes from the KB, and neither seat trusts the marker.
A different deployment shape from koinii's default (N agents funnelling writes through
one single-writer daemon — docs/koinii.md, The deployment shape). There the
daemon IS the shared KB and dereference is a plain read; here each seat is its own
reader with its own store, and the shared reference point is a commit, not a socket.
Complements, not rivals: the daemon for live co-writing, this for disconnected or
independently-replicated seats.
The spike's vocabulary, mapped to real primitives:
kb;
every function takes one, exactly as the other koinii modules do.locator), plus the untrusted transport payload that carries it (the marker).dereference finds the sentence in the seat's own store, reads its
provenance, and hands off to why for the proof.Three ideas, each grounded on a primitive that ships:
"sha256:" followed by 64 lowercase hex chars — over a sentex's canonical
identity (its context, truth polarity, and canonicalized sentence,
docs/canonicalization.md). Three best-practice commitments live in that one string:
dereference
rehashes the resolved form and compares — and SHA-1 has practical chosen-prefix
collisions, so it is the wrong primitive for this job."sha256:" multihash-style tag names the algorithm in
the value itself, so a later migration to another primitive is unambiguous rather
than a silent reinterpretation of 64 anonymous hex chars.pr-str. The digest input is the explicit,
type-tagged, self-delimiting byte encoding of canonical-bytes — independent of
ambient *print-* vars and injective across the value space a sentence holds, so
a symbol never digests as the like-spelled string and (a b) never as (a (b)).
Two seats holding the same assertion compute the same locator, because v/import!
re-canonicalizes every record through the reading build's own constructor.commit-id is the RFC-6962-style
Merkle root over the seat's sorted per-sentex leaf digests — leaf hashing
domain-separated with a 0x00 byte and internal-node hashing with 0x01, so a leaf
can never be forged as an internal node — prefixed "sha256:". Order- and
handle-independent by construction, so two seats that reached the same set of
assertions by different routes compute the same commit id (belief and storage are
order-independent — docs/nmtms.md), and a KB exported, pulled and recovered on
another seat carries the id across. The Merkle shape (over the old flat re-hash) buys
pure auditability: inclusion-proof yields an audit path and verify-inclusion
recomputes the root from just a (locator, proof) pair — no KB — which a flat digest
cannot. commit-id fingerprints knowledge (what two seats compare to agree they
hold the same thing); state-root is a second root whose leaves fold each record's
provenance (:creator + :created + identity), a full snapshot identity like a
git commit (covers who/when), so it moves when provenance moves even when content does
not. publish! / pull! move the bytes (via v/export! / v/import!); the roots
are what say two seats hold the same knowledge (and the same snapshot).dereference finds the sentence in the seat's own KB
and rehashes what it found; a stale or tampered marker fails that check and is
rejected, and a marker the seat cannot resolve means the commit was not received —
never that the marker's payload should be believed. Attribution is trustworthy only
as far as the identity model makes it: a distributed KB inherits the same
cooperative-vs-proof-tier question.Additive, like the other koinii modules: only the public core API plus protocols
(to walk the record store) and resolution (to canonicalize a sentence the store's
own way) — nothing in core loads it.
Koinii cross-seat dereference: the DISTRIBUTED topology, where
independent *seats* — separate processes, each holding its own copy of the KB — stay
in sync by **content-addressed commits** rather than by sharing one live daemon. A
seat asserts a sentence and commits; another seat pulls the same commit and resolves
the same sentence **from its own KB**. The locator travels over a transport; the
proof comes from the KB, and neither seat trusts the marker.
A different deployment shape from koinii's default (N agents funnelling writes through
one single-writer daemon — `docs/koinii.md`, *The deployment shape*). There the
daemon IS the shared KB and dereference is a plain read; here each seat is its own
reader with its own store, and the shared reference point is a commit, not a socket.
Complements, not rivals: the daemon for live co-writing, this for disconnected or
independently-replicated seats.
The spike's vocabulary, mapped to real primitives:
- **seat** — an independent KB-holding process. In this layer a seat is just a `kb`;
every function takes one, exactly as the other koinii modules do.
- **locator / marker** — a content-addressed reference to a canonical assertion (the
`locator`), plus the untrusted transport payload that carries it (the `marker`).
- **transport** — any byte-faithful channel that carries a marker. A marker is a
plain Clojure map, so any transport that ships EDN ships one.
- **the KB** — the sole authority for *meaning*. A marker is never trusted, only
resolved: `dereference` finds the sentence in the seat's own store, reads its
provenance, and hands off to `why` for the proof.
Three ideas, each grounded on a primitive that ships:
- **The locator is content-addressed, not handle-addressed.** A handle is a number
one store minted and does not travel; a locator is a **self-describing** digest — the
literal `"sha256:"` followed by 64 lowercase hex chars — over a sentex's **canonical
identity** (its context, truth polarity, and canonicalized sentence,
`docs/canonicalization.md`). Three best-practice commitments live in that one string:
* **SHA-256, not SHA-1.** A locator is a tamper-detection boundary — `dereference`
rehashes the *resolved* form and compares — and SHA-1 has practical chosen-prefix
collisions, so it is the wrong primitive for this job.
* **Self-describing.** The `"sha256:"` multihash-style tag names the algorithm in
the value itself, so a later migration to another primitive is unambiguous rather
than a silent reinterpretation of 64 anonymous hex chars.
* **A spec'd canonical encoding, not `pr-str`.** The digest input is the explicit,
type-tagged, self-delimiting byte encoding of `canonical-bytes` — independent of
ambient `*print-*` vars and injective across the value space a sentence holds, so
a symbol never digests as the like-spelled string and `(a b)` never as `(a (b))`.
Two seats holding the same assertion compute the **same** locator, because `v/import!`
re-canonicalizes every record through the reading build's own constructor.
- **The commit is a Merkle function of state.** `commit-id` is the RFC-6962-style
**Merkle root** over the seat's *sorted* per-sentex leaf digests — leaf hashing
domain-separated with a `0x00` byte and internal-node hashing with `0x01`, so a leaf
can never be forged as an internal node — prefixed `"sha256:"`. Order- and
handle-independent by construction, so two seats that reached the same set of
assertions by different routes compute the same commit id (belief and storage are
order-independent — `docs/nmtms.md`), and a KB exported, pulled and recovered on
another seat carries the id across. The Merkle shape (over the old flat re-hash) buys
pure auditability: `inclusion-proof` yields an audit path and `verify-inclusion`
recomputes the root from just a `(locator, proof)` pair — no KB — which a flat digest
cannot. `commit-id` fingerprints **knowledge** (what two seats compare to agree they
hold the same thing); `state-root` is a second root whose leaves fold each record's
provenance (`:creator` + `:created` + identity), a full **snapshot** identity like a
git commit (covers who/when), so it moves when provenance moves even when content does
not. `publish!` / `pull!` move the bytes (via `v/export!` / `v/import!`); the roots
are what say two seats hold the same knowledge (and the same snapshot).
- **The marker is untrusted.** `dereference` finds the sentence in the seat's own KB
and rehashes what it found; a stale or tampered marker fails that check and is
rejected, and a marker the seat cannot resolve means the commit was not received —
never that the marker's payload should be believed. Attribution is trustworthy only
as far as the identity model makes it: a distributed KB inherits the same
cooperative-vs-proof-tier question.
Additive, like the other koinii modules: only the public core API plus `protocols`
(to walk the record store) and `resolution` (to canonicalize a sentence the store's
own way) — nothing in core loads it.Koinii dispute reads: two context-scoped views over the engine's whole-KB contradiction surface, plus the small dispute-STATE surface the adjudication driver drives.
The engine represents contradiction but answers it only whole-KB: contradictions
and conflicts each scan the whole store and hand back entries whose sides carry
their own :context. A subscriber wants the per-channel question — 'is there an open
dispute here?' — so that scoping lives in one wrapper rather than being re-derived at
every call site.
'Disputed' is a precise word. It is NOT 'false' and NOT 'defeated-by-strength'.
A dispute is a coexisting clash — S and ¬S both believed with no strength winner, so
argue returns :contradiction and the engine deliberately leaves both standing
(paraconsistent tolerance — Priest's LP, the four-valued argue). A clean
strength-defeat (a :monotonic premise beating a :default one) is the opposite: the
loser is :defeated, argue returns :false, and that is resolved, not disputed.
Two error classes, kept distinct, never merged:
:contradiction — a coexisting :default dilemma (a rebuttal, or a definitional
clash left at equal strength). Both sides believed; argue -> :contradiction.:conflict — an irreducible clash among :monotonic content: two things asserted
known-true that cannot both hold, which the engine has no grounds to prefer. Harder
than a rebuttal, and a caller usually wants to see it alongside.Detection matches the engine, not the intuition. A coexisting dilemma keeps BOTH
sides in?, so why-not reports :believed? true on either — it never reads
:defeated there (that reason is reserved for the strength-defeat, i.e. the resolved
case). So the named-sentence read is argue, whose :contradiction verdict is exactly
'both provable, neither strength-wins' and whose context scoping is exactly 'the asker
sees both sides'. argue is per-sentence and never computes whole-KB contradictions,
which is the perf property the hot path needs.
This module owns the reads, the state vocabulary, and the dispute id. The clock, the timeout value, and the notify sink are the driver's. So the recording functions here take the timestamp (and stale reason) from the caller rather than reading a clock: this module supplies the mechanism, the driver supplies the policy.
Additive, like the sibling koinii modules: only the public core API (argue,
canonical-sentex, sentex-handle) — nothing under vaelii.impl, and nothing in core
loads it.
Koinii dispute reads: two context-scoped views over the engine's whole-KB contradiction surface, plus the small dispute-STATE surface the adjudication driver drives. The engine *represents* contradiction but answers it only whole-KB: `contradictions` and `conflicts` each scan the whole store and hand back entries whose sides carry their own `:context`. A subscriber wants the per-channel question — 'is there an open dispute *here*?' — so that scoping lives in one wrapper rather than being re-derived at every call site. **'Disputed' is a precise word.** It is NOT 'false' and NOT 'defeated-by-strength'. A dispute is a *coexisting* clash — S and ¬S both believed with no strength winner, so `argue` returns `:contradiction` and the engine deliberately leaves both standing (paraconsistent tolerance — Priest's LP, the four-valued `argue`). A clean strength-defeat (a `:monotonic` premise beating a `:default` one) is the opposite: the loser is `:defeated`, `argue` returns `:false`, and that is *resolved*, not disputed. Two error classes, kept distinct, never merged: - **`:contradiction`** — a coexisting `:default` dilemma (a rebuttal, or a definitional clash left at equal strength). Both sides believed; `argue` -> `:contradiction`. - **`:conflict`** — an irreducible clash among `:monotonic` content: two things asserted known-true that cannot both hold, which the engine has no grounds to prefer. Harder than a rebuttal, and a caller usually wants to see it alongside. **Detection matches the engine, not the intuition.** A coexisting dilemma keeps BOTH sides `in?`, so `why-not` reports `:believed? true` on either — it never reads `:defeated` there (that reason is reserved for the strength-defeat, i.e. the resolved case). So the named-sentence read is `argue`, whose `:contradiction` verdict is exactly 'both provable, neither strength-wins' and whose context scoping is exactly 'the asker sees both sides'. `argue` is per-sentence and never computes whole-KB contradictions, which is the perf property the hot path needs. This module owns the *reads*, the state *vocabulary*, and the dispute *id*. The clock, the timeout value, and the notify sink are the driver's. So the recording functions here take the timestamp (and stale reason) from the caller rather than reading a clock: this module supplies the mechanism, the driver supplies the policy. Additive, like the sibling koinii modules: only the public core API (`argue`, `canonical-sentex`, `sentex-handle`) — nothing under `vaelii.impl`, and nothing in core loads it.
Koinii actor identity: per-agent contexts as the identity substrate AND the write boundary, an admin-only agent registry, and the one auth seam whose strength is conditional on the adjudication policy.
The engine deliberately pushes per-caller identity OUT — *creator* is an
unauthenticated annotation and the daemon's only auth is one shared bearer token —
so koinii's answer is the context lattice, not a new auth subsystem:
CxAtlas, lifted under the
channel by (genlCx CxDeploy CxAtlas). A reader of CxDeploy sees the union of
every agent's assertions; CxAtlas alone is 'everything Atlas said' — a plain
context read. Because context is part of sentex identity, Atlas's P and
Boreas's P are two DISTINCT sentexes, each with its own creator, so
first-writer-wins loses no co-source (co-attribution-survives?).The auth seam is conditional on policy (koinii design D4):
*creator* bound by convention, the write routed
to the agent's own context, trusted because the agents are. Correct for a
notify-only deployment. It defends fat-fingers, NOT attackers: authenticate
trusts the claimed id with no proof, so a client may claim any identity. State
plainly that identity is unauthenticated here.authenticate
verifies a credential (the verify-fn seam — sign-at-ingest, an authenticating
proxy, or A2A AgentCards / DIDs) and REFUSES an unverified request; the
write-boundary is enforced at that same seam.Every write goes through the provenance-stamping assert path — NEVER
bulk-assert-facts!, which binds *bulk-load?* and writes no provenance at all.
Koinii actor identity: per-agent contexts as the identity substrate AND the write boundary, an admin-only agent registry, and the one auth seam whose strength is conditional on the adjudication policy. The engine deliberately pushes per-caller identity OUT — `*creator*` is an unauthenticated annotation and the daemon's only auth is one shared bearer token — so koinii's answer is the context lattice, not a new auth subsystem: - **An agent IS its context.** Atlas writes into `CxAtlas`, lifted under the channel by `(genlCx CxDeploy CxAtlas)`. A reader of `CxDeploy` sees the union of every agent's assertions; `CxAtlas` alone is 'everything Atlas said' — a plain context read. Because context is part of sentex identity, Atlas's `P` and Boreas's `P` are two DISTINCT sentexes, each with its own creator, so first-writer-wins loses no co-source (`co-attribution-survives?`). - **The write boundary is 'your own context, and nothing else.'** That is the one enforcement point identity needs, and it is why the registry context is the one context agents may NOT write — the governed may not write the authority that governs them. The auth seam is conditional on policy (koinii design D4): - **Cooperative** (the default) — `*creator*` bound by convention, the write routed to the agent's own context, trusted because the agents are. Correct for a notify-only deployment. It defends fat-fingers, NOT attackers: `authenticate` trusts the claimed id with no proof, so a client may claim any identity. State plainly that identity is unauthenticated here. - **Proof-tier** — REQUIRED the moment trust-resolve is enabled, because trust-weighting a spoofable identity is worse than no trust. `authenticate` verifies a credential (the `verify-fn` seam — sign-at-ingest, an authenticating proxy, or A2A AgentCards / DIDs) and REFUSES an unverified request; the write-boundary is enforced at that same seam. Every write goes through the provenance-stamping `assert` path — NEVER `bulk-assert-facts!`, which binds `*bulk-load?*` and writes no provenance at all.
Koinii speech-acts: the small vocabulary of moves agents make, as sentexes in the KB. A move is not an out-of-band message but knowledge — queryable, retractable, auditable like any other fact — and the SHAPE of the move carries the layer's headline property (koinii design D1 / D5).
Two kinds of move, one split (koinii.md, Reply is an assertion):
asserts, queries) — a plain assertion in the acting agent's
own context. The claim, or the query node, plus its provenance IS the act: nothing
wraps it, because provenance already records who spoke (first-writer-wins). So an
assertion in koinii is just an assertion — assert-claim mints no asserts edge.
A query is minted as a node (pose-query), because a question must be told apart
from a claim.answers, disputes, endorses, justifies) — a META-SENTEX on
the target sentex, naming it by handle (sentexHandle H), asserted in the
RESPONDER's own context and stamped with the responder as creator. Each response
predicate is declared targetFollowingPredicate in CxSpeechActs, so retracting a
target sweeps its replies with it (core/retract-following-metas!). Two facts
force this: the cascade needs BOTH the meta-sentex AND the mark (an unmarked meta
orphans harmlessly), and first-writer-wins forces each act to be its own object —
two endorsers are two sentexes with two creators, never one re-assert.retracts is the engine's retract! on a handle. The error acts
(notUnderstood, refuse) name the received edge, in the refusing agent's context,
and are deliberately unmarked. This layer only REPRESENTS the moves; adjudication is
a separate layer.
Additive, like the sibling koinii modules: requires only the public core API and koinii
identity — nothing under vaelii.impl, and nothing in core loads it. Every write goes
through the provenance-stamping assert path, never bulk-assert-facts!.
Koinii speech-acts: the small vocabulary of moves agents make, as sentexes in the KB. A move is not an out-of-band message but knowledge — queryable, retractable, auditable like any other fact — and the SHAPE of the move carries the layer's headline property (koinii design D1 / D5). Two kinds of move, one split (`koinii.md`, *Reply is an assertion*): - **Origination** (`asserts`, `queries`) — a plain assertion in the acting agent's own context. The claim, or the query node, plus its provenance IS the act: nothing wraps it, because provenance already records who spoke (first-writer-wins). So an assertion in koinii is just an assertion — `assert-claim` mints no `asserts` edge. A query is minted as a node (`pose-query`), because a question must be told apart from a claim. - **Response** (`answers`, `disputes`, `endorses`, `justifies`) — a META-SENTEX on the target sentex, naming it by handle `(sentexHandle H)`, asserted in the RESPONDER's own context and stamped with the responder as creator. Each response predicate is declared `targetFollowingPredicate` in `CxSpeechActs`, so retracting a target sweeps its replies with it (`core/retract-following-metas!`). Two facts force this: the cascade needs BOTH the meta-sentex AND the mark (an unmarked meta orphans harmlessly), and first-writer-wins forces each act to be its own object — two endorsers are two sentexes with two creators, never one re-assert. `retracts` is the engine's `retract!` on a handle. The error acts (`notUnderstood`, `refuse`) name the received edge, in the refusing agent's context, and are deliberately unmarked. This layer only REPRESENTS the moves; adjudication is a separate layer. Additive, like the sibling koinii modules: requires only the public core API and koinii `identity` — nothing under `vaelii.impl`, and nothing in core loads it. Every write goes through the provenance-stamping `assert` path, never `bulk-assert-facts!`.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |