Liking cljdoc? Tell your friends :D

vaelii.impl.koinii.channel

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'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!`.
raw docstring

answerclj

(answer handle content target-handle)

answers: reply to the query at target-handle with content. A ternary meta-sentex (answers agent content (sentexHandle T)) in the agent's own context, creator stamped. The answerer's identity is read off THIS sentex, never off the query. Returns the answer's handle; a re-answer with the same content returns that same handle.

`answers`: reply to the query at `target-handle` with `content`.  A ternary meta-sentex
`(answers agent content (sentexHandle T))` in the agent's own context, creator stamped.
The answerer's identity is read off THIS sentex, never off the query.  Returns the
answer's handle; a re-answer with the same content returns that same handle.
sourceraw docstring

answer-contentclj

(answer-content sentex)

The ?content of an answers move sentex — its third argument.

The `?content` of an `answers` move `sentex` — its third argument.
sourceraw docstring

answers-toclj

(answers-to handle query-handle)

Every believed answer to the query query-handle(answers ?agent ?content (sentexHandle query-handle)), matched anywhere. 'What answered the query, and who said it' is a plain read: pair each with speaker-of for who, and answer-content for what.

Every believed answer to the query `query-handle` — `(answers ?agent ?content
(sentexHandle query-handle))`, matched anywhere.  'What answered the query, and who said
it' is a plain read: pair each with `speaker-of` for who, and `answer-content` for what.
sourceraw docstring

assertclj

(assert handle sentence)
(assert handle sentence opts)

Originate a claim: assert sentence into the agent's OWN context, stamped :creator the agent (merged over any opts), through the medium — for a wire handle that is through the daemon, the single writer. The agent CANNOT write another agent's context from here: the destination is fixed by identity. Returns the handle.

Originate a claim: assert `sentence` into the agent's OWN context, stamped `:creator`
the agent (merged over any `opts`), through the medium — for a `wire` handle that is
through the daemon, the single writer.  The agent CANNOT write another agent's context
from here: the destination is fixed by identity.  Returns the handle.
sourceraw docstring

disputeclj

(dispute handle target-handle)

disputes: challenge the claim at target-handle. Two writes in the agent's own context, both creator-stamped: the REBUTTING claim — the negation of the target's sentence, so the pair surfaces in (contradictions kb) for the dispute reads to scope — and a disputes meta-sentex naming the target. Represents the challenge only; adjudication is a separate policy layer. Returns the dispute edge's handle (write 2).

`disputes`: challenge the claim at `target-handle`.  Two writes in the agent's own
context, both creator-stamped: the REBUTTING claim — the negation of the target's
sentence, so the pair surfaces in `(contradictions kb)` for the dispute reads to scope —
and a `disputes` meta-sentex naming the target.  Represents the challenge only;
adjudication is a separate policy layer.  Returns the dispute edge's handle (write 2).
sourceraw docstring

endorseclj

(endorse handle target-handle)

endorses: stand behind the claim at target-handle. A binary meta-sentex (endorses agent (sentexHandle T)). Two endorsers of one claim are two distinct sentexes with two creators — the case a bare re-assert would collapse to one. Returns the endorsement's handle.

`endorses`: stand behind the claim at `target-handle`.  A binary meta-sentex
`(endorses agent (sentexHandle T))`.  Two endorsers of one claim are two distinct
sentexes with two creators — the case a bare re-assert would collapse to one.  Returns
the endorsement's handle.
sourceraw docstring

endorsements-ofclj

(endorsements-of handle target-handle)

Every believed endorsement naming target-handle(endorses ?agent (sentexHandle target-handle)), matched anywhere. Distinct endorsers are distinct sentexes.

Every believed endorsement naming `target-handle` — `(endorses ?agent (sentexHandle
target-handle))`, matched anywhere.  Distinct endorsers are distinct sentexes.
sourceraw docstring

joinclj

(join medium channel agent-id)
(join medium channel agent-id opts)

Bind agent-id to medium and the coordination channel (koinii design D8). The agent's own context (id/context-forAgentAtlas -> CxAtlas) is lifted under the channel so the channel sees its moves ((genlCx channel CxAtlas)) and rooted so it speaks the reply vocabulary ((genlCx CxAtlas CxSpeechActs), override with :speaks). Both edges are monotonic topology, idempotent, so re-joining is a no-op. Returns the agent handle — {:medium :agent :context :channel} — threaded first into every call below, the network mirror of core's explicit-kb API.

Requires the channel and CxSpeechActs already loaded — the deployment's job — so the targetFollowingPredicate marks that make a reply cascade are in force.

Bind `agent-id` to `medium` and the coordination `channel` (koinii design D8).  The
agent's own context (`id/context-for` — `AgentAtlas` -> `CxAtlas`) is lifted under the
channel so the channel sees its moves (`(genlCx channel CxAtlas)`) and rooted so it
speaks the reply vocabulary (`(genlCx CxAtlas CxSpeechActs)`, override with
`:speaks`).  Both edges are monotonic topology, idempotent, so re-joining is a no-op.
Returns the agent handle — `{:medium :agent :context :channel}` — threaded first into
every call below, the network mirror of core's explicit-`kb` API.

Requires the `channel` and `CxSpeechActs` already loaded — the deployment's
job — so the `targetFollowingPredicate` marks that make a reply cascade are in force.
sourceraw docstring

justifyclj

(justify handle ground target-handle)

justifies: offer ground as a reason for the claim at target-handle. A ternary meta-sentex (justifies agent ground (sentexHandle T)). Returns its handle.

`justifies`: offer `ground` as a reason for the claim at `target-handle`.  A ternary
meta-sentex `(justifies agent ground (sentexHandle T))`.  Returns its handle.
sourceraw docstring

localclj

(local kb)

A channel medium over an in-process KB — the single-process shape. subscribe is a plain core/watch listener; simpler, and correct when every agent is in one process.

A channel medium over an in-process KB — the single-process shape.  `subscribe` is a
plain `core/watch` listener; simpler, and correct when every agent is in one process.
sourceraw docstring

Mediumcljprotocol

The transport a channel runs over — a daemon connection (wire, cross-process) or an in-process KB (local, single-process). Everything above this protocol is written once and runs over either; the two implementations differ only where the engine does — in how a subscription is delivered.

The transport a channel runs over — a daemon connection (`wire`, cross-process) or an
in-process KB (`local`, single-process).  Everything above this protocol is written
once and runs over either; the two implementations differ only where the engine does —
in how a subscription is delivered.

-subscribeclj

(-subscribe medium goal context callback opts)

Register callback for events matching goal in context (nil goal = every change). Returns {:token … :stop (fn []) …}. Where the two media genuinely diverge — a wire poll loop off the agent's thread, vs an in-process listener.

Register `callback` for events matching `goal` in `context` (nil `goal` = every
change).  Returns `{:token … :stop (fn []) …}`.  Where the two media genuinely
diverge — a wire poll loop off the agent's thread, vs an in-process listener.

-sentexclj

(-sentex medium handle)

The stored sentex handle names, as a map (:sentence …).

The stored sentex `handle` names, as a map (`:sentence` …).

-assertclj

(-assert medium sentence context opts)

Assert sentence in context with opts (carrying :creator), returning the handle. The daemon stamps :creator from the opts — the cooperative identity annotation crossing the wire (identity), since *creator* is a var in the daemon's process, not the client's.

Assert `sentence` in `context` with `opts` (carrying `:creator`), returning the
handle.  The daemon stamps `:creator` from the opts — the cooperative identity
annotation crossing the wire (`identity`), since `*creator*` is a var in the daemon's
process, not the client's.

-feed-pollclj

(-feed-poll medium token cursor opts)

Read a raw subscription forward — {:events :cursor :lagged}. :lagged non-zero is the whole point of catch-up: the cursor fell off the ring. Local THROWS, as above.

Read a raw subscription forward — `{:events :cursor :lagged}`.  `:lagged` non-zero is
the whole point of catch-up: the cursor fell off the ring.  Local THROWS, as above.

-matchingclj

(-matching medium sentence context)

The believed sentexes matching sentence in context (a ?ctx matches anywhere).

The believed sentexes matching `sentence` in `context` (a `?ctx` matches anywhere).

-check-editclj

(-check-edit medium batch)

check-edit over an {:add […] :remove […]} batch — the dry run, storing nothing. A vector of problems, empty when admissible.

`check-edit` over an `{:add […] :remove […]}` batch — the dry run, storing nothing.
A vector of problems, empty when admissible.

-editclj

(-edit medium batch)

Apply an {:add […] :remove […]} batch in one settle.

Apply an `{:add […] :remove […]}` batch in one settle.

-feed-openclj

(-feed-open medium goal context)

Open a raw change-feed subscription with a cursor — {:token :cursor :max-events}. The wire feed's cursor primitive that catch-up (catchup) resumes from; -subscribe wraps it for the happy path, this exposes it for the durable-cursor / lag case. A local (in-process) medium THROWS: core/watch is callback-based with no ring or cursor, so there is nothing to fall off and nothing to resume.

Open a raw change-feed subscription with a cursor — `{:token :cursor :max-events}`.
The wire feed's cursor primitive that catch-up (`catchup`) resumes from; `-subscribe`
wraps it for the happy path, this exposes it for the durable-cursor / lag case.  A
local (in-process) medium THROWS: `core/watch` is callback-based with no ring or
cursor, so there is nothing to fall off and nothing to resume.

-queryclj

(-query medium goal context)

Solutions for goal in context as binding maps — the CONE-AWARE read (walks the genlCx cone, unlike -matching, so a channel read sees its agents' own-context sentexes). The snapshot half of catch-up (catchup) reads through this.

Solutions for `goal` in `context` as binding maps — the CONE-AWARE read (walks the
genlCx cone, unlike `-matching`, so a channel read sees its agents' own-context
sentexes).  The snapshot half of catch-up (`catchup`) reads through this.
sourceraw docstring

open-queriesclj

(open-queries handle)

Every query node on the channel — (queries ?agent ?question), matched anywhere. The catch-up read: an agent connecting LATER reads the open questions from the durable KB, decoupled in time from whoever asked and whether they are still online.

Every query node on the channel — `(queries ?agent ?question)`, matched anywhere.  The
catch-up read: an agent connecting LATER reads the open questions from the durable KB,
decoupled in time from whoever asked and whether they are still online.
sourceraw docstring

pose-queryclj

(pose-query handle question)

Originate a query NODE (queries agent question) in the agent's own context — the thing a responder later answers by handle. Minted (unlike a plain assert) because a question must be told apart from a claim. Returns its handle.

Originate a query NODE `(queries agent question)` in the agent's own context — the
thing a responder later `answer`s by handle.  Minted (unlike a plain
`assert`) because a question must be told apart from a claim.  Returns its handle.
sourceraw docstring

queryclj

(query handle goal context)

CONE-AWARE solutions for goal in context, as binding maps — the read that sees a channel's agents' own-context sentexes up the genlCx cone (unlike the direct sentexes-matching the reads above use). It answers the channel's whole current view of goal, which is what catch-up (catchup) snapshots the state from.

CONE-AWARE solutions for `goal` in `context`, as binding maps — the read that sees a
channel's agents' own-context sentexes up the genlCx cone (unlike the direct
`sentexes-matching` the reads above use).  It answers the channel's whole current view of
`goal`, which is what catch-up (`catchup`) snapshots the state from.
sourceraw docstring

reply-manyclj

(reply-many handle sentences)

Assert MORE THAN ONE linked claim as one reply — VALIDATE the whole batch with check-edit first, then commit it with edit! in one settle. edit! is not atomic on a throw (docs/feed.md), so a mid-batch failure would leave a HALF-reply in the shared truth; the dry run refuses an inadmissible batch before anything lands. Each of sentences is asserted into the agent's own context, creator stamped.

Throws :koinii/reply-inadmissible (carrying the check-edit :problems) if the batch is not admissible. Returns edit!'s result on success.

Assert MORE THAN ONE linked claim as one reply — VALIDATE the whole batch with
`check-edit` first, then commit it with `edit!` in one settle.  `edit!` is not atomic
on a throw (docs/feed.md), so a mid-batch failure would leave a HALF-reply in the shared
truth; the dry run refuses an inadmissible batch before anything lands.  Each of
`sentences` is asserted into the agent's own context, creator stamped.

Throws `:koinii/reply-inadmissible` (carrying the `check-edit` `:problems`) if the batch
is not admissible.  Returns `edit!`'s result on success.
sourceraw docstring

speaker-ofclj

(speaker-of sentex)

Who made the response move sentex — its ?agent, the FIRST argument of the meta-sentex. Read off the reply's own sentence (so it crosses the wire without a provenance op) and equal to the :creator provenance the daemon stamped: the conversation is in the graph, not in the client.

Who made the response move `sentex` — its `?agent`, the FIRST argument of the
meta-sentex.  Read off the reply's own sentence (so it crosses the wire without a
provenance op) and equal to the `:creator` provenance the daemon stamped: the
conversation is in the graph, not in the client.
sourceraw docstring

subscribeclj

(subscribe handle goal context callback)
(subscribe handle goal context callback opts)

Call callback when a claim matching goal appears or changes in context — the async reply trigger. goal nil watches every change; a goal refused by core/watch (an aggregate, unknown, an evaluable, …) is refused identically here, since it is the same check. Watch the CHANNEL context to see every agent's moves (the feed is scoped up the genlCx cone, so a channel watch delivers a write made in an agent's own context).

For a wire handle the poll loop runs OFF the agent's own thread — the whole reason the design uses the wire feed rather than in-process watch, since an in-process callback runs on the single writer's thread and one slow agent would otherwise stall every writer. opts: :wait-ms (long-poll, wire only), :on-lagged (fn of the dropped count — resync from the KB), :on-error (fn of a throwable).

Returns a subscription {:token … :stop (fn []) …}; call :stop to drop it (wire: it also wakes the parked poll). Decoupled in time: a subscription is FORWARD-ONLY — it never retroactively receives a write made before it registered. History that predates it is recovered by READING the KB (answers-to / sentexes-matching), the durable half of the channel; the feed is the live half.

Call `callback` when a claim matching `goal` appears or changes in `context` — the
async reply trigger.  `goal` nil watches every change; a `goal` refused by `core/watch`
(an aggregate, `unknown`, an evaluable, …) is refused identically here, since it is the
same check.  Watch the CHANNEL context to see every agent's moves (the feed is scoped up
the `genlCx` cone, so a channel watch delivers a write made in an agent's own context).

For a `wire` handle the poll loop runs OFF the agent's own thread — the whole reason the
design uses the wire feed rather than in-process `watch`, since an in-process callback
runs on the single writer's thread and one slow agent would otherwise stall every
writer.  `opts`: `:wait-ms` (long-poll, wire only), `:on-lagged` (fn of the dropped
count — resync from the KB), `:on-error` (fn of a throwable).

Returns a subscription `{:token … :stop (fn []) …}`; call `:stop` to drop it (wire: it
also wakes the parked poll).  Decoupled in time: a subscription is FORWARD-ONLY — it
never retroactively receives a write made before it registered.  History that predates
it is recovered by READING the KB (`answers-to` / `sentexes-matching`), the durable half
of the channel; the feed is the live half.
sourceraw docstring

unsubscribeclj

(unsubscribe subscription)

Drop a subscription (from subscribe). Idempotent.

Drop a `subscription` (from `subscribe`).  Idempotent.
sourceraw docstring

voteclj

(vote handle stance target-handle)

Cast a ballot on the claim at target-handle: stance :for -> (votesFor agent (sentexHandle T)), :against -> (votesAgainst agent (sentexHandle T)). A meta-sentex in the agent's own context, creator stamped — a coordination move like endorse, but one a resolution policy COUNTS rather than merely records: adjudication/resolve-by- majority! tallies these ballots and upholds the side with strictly more, leaving a tie honestly OPEN (a split house decides nobody). Idempotent by sentence identity — one ballot per agent per stance; to change a vote, retract the old ballot first. Returns the ballot's handle.

Cast a ballot on the claim at `target-handle`: `stance` `:for` -> `(votesFor agent
(sentexHandle T))`, `:against` -> `(votesAgainst agent (sentexHandle T))`.  A meta-sentex
in the agent's own context, creator stamped — a coordination move like `endorse`, but
one a resolution policy COUNTS rather than merely records: `adjudication/resolve-by-
majority!` tallies these ballots and upholds the side with strictly more, leaving a tie
honestly OPEN (a split house decides nobody).  Idempotent by sentence identity — one
ballot per agent per stance; to change a vote, retract the old ballot first.  Returns the
ballot's handle.
sourceraw docstring

wireclj

(wire conn)

A channel medium over a daemon connection conn (from vaelii.client/client) — the cross-process shape. Every write funnels through the daemon (the single writer) and subscribe polls the wire feed off the agent's thread.

A channel medium over a daemon connection `conn` (from `vaelii.client/client`) — the
cross-process shape.  Every write funnels through the daemon (the single writer) and
`subscribe` polls the wire feed off the agent's thread.
sourceraw docstring

writer-orderclj

Documentation of the consistency model, not a runtime value (koinii design D7).

The daemon serializes every write through one monitor (docs/operations.md) — it is the single writer. So the change-feed cursor delivers a TOTAL ORDER: every agent reads the same events in the same sequence, and an agent can only reply to a claim it has already seen (its reply is a later write than the claim, in the one order). The happy path is therefore causally sound BY CONSTRUCTION — a reply never precedes its target in the order any agent observes.

Logical clocks (Lamport / vector) are redundant here, and that is deliberate. They exist to reconstruct a causal order across CONCURRENT writers; with one writer the store order already IS the causal order, so a timestamp would add a field nothing reads. A future reader reaching for one should first ask whether koinii has gone multi-writer — if it has not, the total order is already the guarantee the clock would rebuild. If it ever does (multiple daemons over sharded KBs), THAT is when causal metadata becomes load bearing; until then it is ceremony.

**Documentation of the consistency model, not a runtime value** (koinii design D7).

The daemon serializes every write through one monitor (docs/operations.md) — it is the
single writer.  So the change-feed cursor delivers a TOTAL ORDER: every agent reads the
same events in the same sequence, and an agent can only reply to a claim it has already
seen (its reply is a later write than the claim, in the one order).  The happy path is
therefore causally sound BY CONSTRUCTION — a reply never precedes its target in the
order any agent observes.

**Logical clocks (Lamport / vector) are redundant here, and that is deliberate.**  They
exist to reconstruct a causal order across CONCURRENT writers; with one writer the store
order already IS the causal order, so a timestamp would add a field nothing reads.  A
future reader reaching for one should first ask whether koinii has gone multi-writer —
if it has not, the total order is already the guarantee the clock would rebuild.  If it
ever does (multiple daemons over sharded KBs), THAT is when causal metadata becomes load
bearing; until then it is ceremony.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close