@projectpac/flow-joint-search
v0.6.0
Published
Part of PAC: @projectpac/flow-joint-search.
Readme
@projectpac/flow-joint-search
Two principals have a question neither one's data can answer alone, and neither will hand their data to the other.
This is the flow the rest of PAC exists for, and it negotiates twice.
First over what to compute. Each side sees only the other's descriptor -- source names and a sentence each, never contents -- and the two agents write objectives into a shared versioned resource: one suggests, the other reads the diff and suggests back, until one agrees. An objective names, per node, exactly which sources it may read, what the terms in it mean, the rules a program must follow, and what may and may not come out.
Then over how. The same machinery produces program.py -- real Python
defining run(parties), which is what the box's runner executes -- and both
sides approve those exact bytes. The instructions carry the box's own execution
contract: the party order, the shape each party's input arrives in, the modules
that may be imported, the bounds, and the rule that nothing may depend on the
clock or randomness. Only then does anything private move, and it moves into a
box both sides verified, carrying exactly the sources the agreed objective
named.
Everything hard here belongs to a component: the versioned resource and its
bundles are the artifact store's, the runs and their output contract the loop's,
the sessions and delivery the adapter's, and every step that touches material is
an apply. What is left is the state machine -- who initiates, what each side
owes next, and when a negotiation has gone on long enough.
And what the flow says about itself, it says itself. It declares its route to
the network, advertises that this node runs it under kind: "flow" -- which is
also how it finds its peers: a node that advertises joint-search under that
kind runs it, and nothing else can answer -- declares its search intent to
the router from an optional scope, so a node with no router still runs it,
keeps its records through ctx.records, and runs its own timers. The host
says none of it.
| | |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| routes | POST /queries, GET /queries, GET /tasks, GET /works, GET /descriptor |
| payloads | probe.* (this flow's), plan.* and program.* (the rounds' wire), work.* (the box drive's), task.failed |
| records | queries, verdicts, tasks, works: the state machine's memory, kept through ctx.records |
| background | publish 30s, triage 5s, pair 5s, advance 2s, sweep 30s, each on an interval of its own |
| needs | the sources named in sources -- which of their offers is the negotiation's own answer -- and a box adapter, whose egress allowlist is what this node will let a run reach |
| intents | search, declared to the router from an optional scope; not the fallback, and publishing a query needs nobody to have offered anything first |
| results | one search per task, carrying its works' summaries and outputs, declared to ctx.results from an optional scope: GET /flows/results/results reads them beside every flow's |
Roles are elected from the two node ids, so both sides derive the same
initiator without being told. Seven files: pairing.ts is everything before
a negotiation, negotiation.ts the three engines' specs, runs.ts what each
kind of answer means, prompts.ts what each run is asked (and the box
contract), state.ts the durable records, flow.ts the manifest, the config
and the handle, index.ts the wiring and the routes.
Running it live needs a real box, because the in-process double cannot join two
processes: see devtools/README.md. The scripted
path is tests/joint-search.spec.ts, which is also the
reference for what a negotiation script looks like.
Running it
pnpm check # includes tests/joint-search.spec.ts: the scripted negotiation
just demo # headless floor: a directory, two seeded nodes, alike, down
MODEL=pi just dev # the real lane: panes, sources, and the box -- then, in another terminal:
just query "find the spotify artists we both listen to"just dev owns the terminal it runs in, so the query goes in another one, once
the seed pane says joint search seeded. just a1 api GET
/flows/joint-search/works shows every stage on the way to done.
The sibling layout
Cross-repo dependencies are link: paths into checkouts beside this one --
nothing of PAC's is fetched from a registry. Clone the repos this one links
(the link: values in the package manifests) into the same parent directory,
run pnpm install, and everything resolves from source. The flow's three
runtime dependencies beyond the sdk -- the negotiation engine, prompting, and
plugin-lib -- are pinned by version and redirected to the checkouts by
pnpm-workspace.yaml. The flow's whole-node suite lives in tests/, standing
up real nodes from the sibling pac-node.
The design repo
(pac) is the front door: what PAC is, and how the repos fit together.
