Skip to content

examples: EigenTrust on propagators via Racket FFI#32

Draft
kumavis wants to merge 3 commits into
mainfrom
claude/setup-racket-grammar-tests-OekFZ
Draft

examples: EigenTrust on propagators via Racket FFI#32
kumavis wants to merge 3 commits into
mainfrom
claude/setup-racket-grammar-tests-OekFZ

Conversation

@kumavis

@kumavis kumavis commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

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"))

claude added 3 commits April 27, 2026 23:16
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 kumavis marked this pull request as draft April 28, 2026 21:04
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants