examples: EigenTrust on propagators via Racket FFI#32
Draft
kumavis wants to merge 3 commits into
Draft
Conversation
Demonstrates Prologos using its underlying CALM-monotone propagator
network to run the classic EigenTrust [Kamvar/Schlosser/Garcia-Molina
2003] reputation algorithm. First-class propagator surface syntax is
on the roadmap but not yet wired through type checking, so the example
imports `net-new-cell`, `net-add-propagator`, and `run-to-quiescence`
via `foreign racket "..."` declarations and composes them from a
.prologos source.
Files:
- `racket/prologos/foreign.rkt` — extends the FFI marshaller with
Posit32 and Posit64. The bit-pattern integer is the carrier (matching
`posit-impl.rkt`), so foreign-imported posit ops compose without
conversion ceremony.
- `racket/prologos/lib/examples/eigentrust-prop.rkt` — the FFI shim.
Wraps the persistent prop-network in a small mutable handle and
exposes:
et-new, et-cell, et-cell-get, et-cell-set,
et-sum-prop, et-run, et-snapshot, et-run-and-snapshot,
et-add-iter (one EigenTrust round)
Cells use a generation-tagged merge so iteration k+1 writes strictly
dominate iteration k initial values; every cell is written exactly
once per round and propagators fire in dataflow order.
All "side-effecting" calls return a meaningful Nat (handle id, cell
id, or `final-cells` list) instead of Unit. Prologos's reduction is
call-by-name; an unused result expression would never be evaluated
and the side effect would be silently dropped. Threading a Nat
through every call forces strict evaluation order.
- `racket/prologos/lib/examples/eigentrust.prologos` — the algorithm.
Builds a 4-peer trust graph, drives 20 power iterations with
α = 0.15, and returns the converged `[List Posit32]`. Output matches
the reference implementation exactly:
[0.0652, 0.4348, 0.0652, 0.4348] (sum = 1.0)
Run with the small driver pattern below; the parameterize is required
on Racket 8.x because the project's BSP scheduler uses `(thread #:pool
'own)` which is a Racket 9 feature:
(require (file "racket/prologos/driver.rkt")
(only-in (file "racket/prologos/propagator.rkt")
current-parallel-executor sequential-fire-all))
(parameterize ([current-parallel-executor sequential-fire-all])
(process-file "racket/prologos/lib/examples/eigentrust.prologos"))
Per the design-mantra audit (.claude/rules/on-network.md, propagator-design.md):
"All-at-once, all in parallel, structurally emergent
information flow ON-NETWORK."
The previous shim allocated one cell per (peer, iteration) and installed
N independent sum-propagators per round — a classic N-propagator
step-think pattern. This rewrite collapses each iteration to:
* ONE compound cell holding the full Posit32 score vector
(gen-tagged immutable vector; gen-merge keeps the higher generation).
* ONE broadcast propagator (`net-add-broadcast-propagator`,
BSP-LE Track 2 Phase 1B) covering all N peer updates as items —
parallel-decomposable across OS threads at fire time.
Carriers throughout are Posit32 — handle ids, cell-refs, fuel, scores,
weights, decay. The FFI surface is the three primitives the user named:
net-new : Posit32 -> Posit32
net-new-cell : Posit32 [List Posit32] -> Posit32
net-add-prop : Posit32 Posit32 Posit32 [List [List Posit32]] [List Posit32] Posit32 -> Posit32
net-run-read : Posit32 Posit32 -> [List Posit32]
The .prologos source uses list literals (`'[0.0 0.5 0.5 0.0]`) instead
of cons chains for the trust matrix and pretrust, and bare decimal
posit literals (no tilde prefix). Bracketing is reduced to where it's
mandatory (function applications); top-level expressions and let-RHS
positions use whitespace.
Tests pass under Racket 9.1 (built from source —
download.racket-lang.org is on a curl allowlist, so source must come
from github.com/racket/racket and be hand-built). The pre-existing
`#:pool 'own` failure on Racket 8.x is gone.
Pitfalls discovered during the bring-up are catalogued in
docs/tracking/2026-04-28_ETPROP_PITFALLS.md (15 entries: def is
reference-transparent, reduction is call-by-name and drops unused
let-bound side effects, foreign type signatures must fit on one line,
defn arity-1 list pattern dispatch produces a non-exhaustive match,
Racket 9 packaging vs offline catalog, ...).
Final converged scores match the Python reference exactly:
[0.0652, 0.4348, 0.0652, 0.4348] (sum = 1.0)
Asked: "what MUST stay on the Racket side?". Answer (now codified at
the top of docs/tracking/2026-04-28_ETPROP_PITFALLS.md):
1. Propagator fire functions (Racket closures invoked by the
scheduler; Prologos lambdas can't cross the FFI boundary).
2. Cell merge functions (same constraint).
3. The cell-value carrier (gen-tagged immutable Racket vector).
4. FFI marshalling glue (cons/nil chain walking, bit-pattern
extraction).
Everything else moves to .prologos. The Racket-side `net-add-prop` is
now purpose-AGNOSTIC: it implements the generic affine combination
out[j] := bias[j] + Σ_i weight[j][i] · in[i]
with no knowledge of EigenTrust, decay, transposition, or pretrust.
The .prologos source now does the entire algorithm:
* `transpose` — pure Prologos, on List (List Posit32)
* `scale-mat` — pure Prologos
* `scale-vec` — pure Prologos
* `zeros-of` — pure Prologos
* `drive` — K-step iteration driver, in Prologos
* `eigentrust` — composes the above:
weights = (1 − α) · transpose(C)
biases = α · pretrust
and hands them to `net-add-prop`. The Racket side never sees the
matrix or the decay constant.
Mantra alignment is preserved: each iteration is still ONE compound
cell + ONE broadcast propagator (parallel-decomposable across OS
threads at fire time per BSP-LE Track 2 Phase 1B). The cell DAG
iter-0 → iter-K still drives firing order via dataflow.
Result still matches the Python reference (sum = 1.0):
[0.0652, 0.4348, 0.0652, 0.4348]
Tests: 99 across foreign, foreign-block, pvec under Racket 9.1.
kumavis
pushed a commit
that referenced
this pull request
May 6, 2026
…nference Codifies the elaborator bug discovered during OCapN Phase 25 (handshake modelling, commit 4b92416). Adding a SECOND constructor to a `data` type causes importing modules' multi-arg helpers to fail "Could not infer type" when they call a function returning that data type with 2+ bound arguments. Empirical probe matrix (from the Phase 25 work): | shape | defines? | |------------------------------------------------------|----------| | 1-arg + let + captp-call (constant other args) | yes | | 2-arg + let + captp-call (1 bound + 1 constant) | NO | | 3-arg + let + captp-call (3 bound) | NO | | 2-arg + INLINE + captp-call (no let, all bound) | yes | | Reverting to single-ctor data type | yes | The single-constructor workaround (move the optional payload into the existing type as a list/option field) is the uniform fix — it's what Phase 25 ended up using by adding `pending-out` to BridgeState rather than a second BridgeStep constructor. Worth a Prologos issue because the failure mode (importing modules fail to compile) is far enough from the surface cause (data def in a different module) that diagnosis took ~1 h of probing with shrinking test cases.
kumavis
pushed a commit
that referenced
this pull request
May 6, 2026
kumavis
pushed a commit
that referenced
this pull request
May 6, 2026
Pulled from origin/claude/ocapn-prologos-implementation-auLxZ:
LIBS (16 total now, was 7):
+ bridge-interop-helpers, captp-bridge, captp-session, captp-wire,
netlayer, pipelining, syrup-wire, tcp-testing, vat (9 new)
~ refreshed: syrup, promise, message, refr, behavior, locator, core
Total: ~3.3 kLOC of OCapN library code.
TESTS (20 kept from upstream's 26):
OK (13 files, 145 test cases passing on this branch's nf):
acceptance-l3 (10), behavior (13), captp (7), e2e (8), locator
(13), message (19), netlayer (14), pipeline (5), pipelining (4),
promise (16), refr (6), syrup (9), vat (21).
SKIP (7 files, self-skip when Node.js absent — Phase 24 interop):
abort, bridge-interop, conversation, handshake, live-interop,
pipelined, rpc.
OMITTED FROM SYNC (6 files dropped because they fail to load on this
branch's compiler):
- test-ocapn-bridge, captp-wire, syrup-wire, syrup-cross-impl,
netlayer-tcp: each imports prologos::ocapn::syrup-wire which
elaborates with a "Type mismatch" error on this branch's
elaborator. Upstream's syrup-wire was developed against a
compiler version slightly diverged from this branch's; when
the elaborator gap closes (or that lib's source updates), they
can be re-pulled.
- test-ocapn-tcp-testing: imports a missing `tcp-ffi.rkt` Racket
module that's not in this branch's tree.
OTHER ARTIFACTS pulled:
+ examples/2026-04-27-ocapn-acceptance.prologos (used by
test-ocapn-acceptance-l3; 126 LOC)
~ docs/tracking/2026-04-27_GOBLIN_PITFALLS.md (1129 -> 1260 LOC;
upstream added pitfalls #31, #32)
NOTES.md updated to document the sync (date, what was pulled,
test status table, omitted files + reasons).
Net delta for this branch's CI:
+ 7 new test files passing (captp, netlayer, pipelining, e2e,
pipeline, vat, acceptance-l3) = +69 test cases.
+ 7 new test files self-skipping when Node.js absent.
All 12 OCapN-hybrid programs (examples/ocapn/ocapn-hybrid-*.prologos)
continue to run on the hybrid kernel unchanged.
https://claude.ai/code/session_01Tycs6BWKG58Wo99YVPg6DF
kumavis
pushed a commit
that referenced
this pull request
May 6, 2026
…or spec-level resolution Companion to pitfall #32 / issue #60. Discovered during OCapN Phase 25.4 (commit f633e5a) while wiring verify-questioner-reply in bridge-interop-helpers.prologos. When module M3 :refer-all's M2, which :refer-all's M1 (defining type T), M3's function specs mentioning T fail to type-check against function calls returning T from M1: the spec's unqualified `T` and the body's fully-qualified `prologos::M1::T` are treated as different types by the elaborator, even though they reference the same definition. Type mismatch expected: [Pi [x BridgeStep] [Pi [y Nat] [Option PromiseState]]] got: [Pi [x BridgeStep] [Pi [y Nat] [Option prologos::ocapn::promise::PromiseState]]] Workaround: add an explicit `:refer [TypeName ...]` import in M3 (even though :refer-all transitively re-exports it). Cost: one line per affected module per affected type. Frequency in OCapN work: hit once in Phase 25.4 (PromiseState in bridge-interop-helpers). Lower impact than #60 because the workaround is mechanical (add the explicit refer line), but worth tracking because: 1. The error message looks like a tautology ("T != T") at first glance; needs careful reading to see the qualifier. 2. :refer-all chains are common in Prologos library modules. 3. Fixing it would make :refer-all behave as users expect. To be filed as a separate issue from #60 — different symptom, different mechanism, different fix surface.
kumavis
pushed a commit
that referenced
this pull request
May 6, 2026
…weak refs deferred
Plans Phases 31-34 of OCapN: distributed reference counting via
op:gc-export and op:gc-answer messages.
Key insight: CapTP GC is a protocol (explicit refcount-decrement
messages over the wire), NOT a host-language collection algorithm.
Weakmaps and finalization are NOT required for protocol correctness
— they're an ergonomic layer for automatic detection of "user
dropped this Refr" that would otherwise need explicit release calls.
Recommended phasing:
Phase 31 — Refcount tables in BridgeState
Two new fields: exports-refcount + imports-refcount.
Plain (Nat -> Nat) maps. Mechanical field additions.
Phase 32 — Outbound release-* API + wire bytes
User-facing release-import + release-answer on
ConnectionState. Returns ConnRelease(cs', bytes-list).
Manual call from user code.
Phase 33 — Inbound op:gc-* dispatch
Replace current "append to audit log" no-op arms with
real refcount-decrement + answer-table cleanup.
Phase 34 — DEFERRED: automatic release via Racket weak hash + finalizer
Hooks host-language GC. Layering on top of manual.
Estimate: ~half a day for Phases 31-33; Phase 34 is 1-2 days
when/if automatic release becomes worth the complexity.
Doc covers:
- What we already have (wire codec + audit-log dispatch)
- What's missing (refcount semantics, outbound API, real inbound dispatch)
- Why manual first (testability, deterministic failure modes, clean layering)
- Outbound timing (user signals completion, bridge doesn't unilaterally decide)
- Open questions for the implementation phase
- Risk summary against pitfalls #18, #32 (#60), #33 (#61)
No code changes — purely planning. Implementation comes when this
work is prioritized.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Demonstrates Prologos using its underlying CALM-monotone propagator
network to run the classic EigenTrust [Kamvar/Schlosser/Garcia-Molina
2003] reputation algorithm. First-class propagator surface syntax is
on the roadmap but not yet wired through type checking, so the example
imports
net-new-cell,net-add-propagator, andrun-to-quiescencevia
foreign racket "..."declarations and composes them from a.prologos source.
Files:
racket/prologos/foreign.rkt— extends the FFI marshaller withPosit32 and Posit64. The bit-pattern integer is the carrier (matching
posit-impl.rkt), so foreign-imported posit ops compose withoutconversion ceremony.
racket/prologos/lib/examples/eigentrust-prop.rkt— the FFI shim.Wraps the persistent prop-network in a small mutable handle and
exposes:
et-new, et-cell, et-cell-get, et-cell-set,
et-sum-prop, et-run, et-snapshot, et-run-and-snapshot,
et-add-iter (one EigenTrust round)
Cells use a generation-tagged merge so iteration k+1 writes strictly
dominate iteration k initial values; every cell is written exactly
once per round and propagators fire in dataflow order.
All "side-effecting" calls return a meaningful Nat (handle id, cell
id, or
final-cellslist) instead of Unit. Prologos's reduction iscall-by-name; an unused result expression would never be evaluated
and the side effect would be silently dropped. Threading a Nat
through every call forces strict evaluation order.
racket/prologos/lib/examples/eigentrust.prologos— the algorithm.Builds a 4-peer trust graph, drives 20 power iterations with
α = 0.15, and returns the converged
[List Posit32]. Output matchesthe reference implementation exactly:
[0.0652, 0.4348, 0.0652, 0.4348] (sum = 1.0)
Run with the small driver pattern below; the parameterize is required
on Racket 8.x because the project's BSP scheduler uses
(thread #:pool 'own)which is a Racket 9 feature: