Ants have no manager. An ant wanders, finds food, and dribbles a chemical on the way home; other ants prefer strong-smelling ground, so good paths reinforce themselves and bad ones evaporate. Pheromone Trails points that mechanism at research: five agents walk a graph of forty evidence fragments looking for evidence that bears on a question, and strengthen the connections that paid off. The output is a map, not a summary — strong connections thick and bright, dead ends faded — and you click the brightest corridor to read the actual multi-fragment chain with every source and score exposed.
The corpus is the graph and the graph is the data: forty Evidence nodes joined by EvidenceLink edges that carry their own mutable state — kind, relevance, and a pheromone value that rises when an agent finds a path useful and decays every tick. Coordination happens entirely through that shared edge state; no agent sends a message or knows another exists.
The property that makes five concurrent agents testable is a propose/reduce split. ForageTick spawns on an agent's current fragment, scores every incident edge, and returns exactly one Proposal — it writes nothing. ReduceTick is the single writer: it sorts proposals into a canonical order (fid|dst_eid, never arrival order), applies deposits clamped to PHER_CAP, runs one global evaporation pass clamped to PHER_FLOOR, and records a Receipt. Exploration randomness is sha256(seed | tick | agent | candidate) rather than a PRNG stream, because Python's hash() is salted per process and a shared random.Random makes each draw depend on the order agents were iterated. The result is that the same seed reproduces byte-identical receipts, which is what the replay scrubber and the integrity Warden both rest on. Trail terms are forced to zero for fragments an agent has already read — a trail is a tip from someone else, never a reason to re-read your own notes. All state is root-reachable, so it persists across a process restart with no database and no serialisation code.
Requires Jac 0.34.6 exactly (pip install jaclang gives a different generation of the language):
curl -fsSL https://raw.githubusercontent.com/jaseci-labs/jaseci/main/scripts/install.sh | bash -s -- --version 0.34.6
cd pheromone
jac install byllm
jac start main.jac # serve the app -> http://localhost:8000
# jac run # dev mode with hot reload; spawns a separate vite child
jac clean --data && jac test # engine suite, 25 tests
jac run eval.jac # evaluation sweep, 3 hypotheses x 20 seeds
jac run corpus_check.jac # validates every corpus invariant
jac run framing_check.jac # validates the scorer
jac run golden_path_check.jac # end-to-end acceptance
jac clean --data before a test run is not optional: jac run persists into .jac/, and stale anchors meeting recompiled archetypes produce NodeAnchor ... is not a valid reference!.
Two operational notes that cost us hours:
jac start and jac run spawn processes that pkill -f "jac start" does not fully
match. An orphaned server keeps its port and the new one silently takes the next, so you
end up talking to a stale process. Kill jac start, vite.js and jac run, then confirm
exactly one listener before trusting anything..sv.jac change and can fail the non-dev rollup
build with errors that make no sense ("BottomStrip" is not exported while jac check is
clean). Fix: rm -rf .jac/client/compiled .jac/client/.vite .jac/cache — never
.jac/data, which holds the warmed score cache.Eleven walker:pub endpoints — LoadCase · GetState · ResetCase · StartRun · StepRun · RunToEnd · ToggleFragment · RunBaseline · ReplayTo (in swarm.sv.jac) and RunWarden · InjectPoison (in warden.sv.jac).
Plus nine def:pub functions: list_providers · set_provider · propose_framings · ensure_scores · score_source · narrate_corridor · audit_sessions · concentration · restart_server.
Everything runs with no API key at all — the deterministic stemmed-keyword scorer is always available and is what the golden-path demo uses. A model is an upgrade, not a dependency.
Create pheromone/.env (gitignored):
FIREWORKS_AI_API_KEY=fw_your_key
FIREWORKS_SLUG=accounts/fireworks/models/gpt-oss-120b
OLLAMA_MODEL=gemma-4-e4b # optional, fully offline
GOOGLE_API_KEY= # optional
ANTHROPIC_API_KEY= # optional
FIREWORKS_SLUG is the bare slug — the code prepends fireworks_ai/. List what your key
can reach rather than guessing:
curl -s https://api.fireworks.ai/inference/v1/models \
-H "Authorization: Bearer $FIREWORKS_AI_API_KEY" \
| python3 -c "import sys,json;[print(m['id']) for m in json.load(sys.stdin)['data']]"
Then export the env before launching the server — the server reads the process
environment, so .env alone is not enough:
set -a && source .env && set +a && jac start main.jac
Warm the cache before you rely on it. The first model-scored run makes three batched calls per hypothesis and takes roughly a minute; every run after that is instant because the scores live on the graph. Warming must go through the API — a CLI script warms a different root:
API=http://localhost:8000
for h in H1 H2 H3; do
curl -s -X POST $API/walker/ResetCase -H "Content-Type: application/json" -d '{}' >/dev/null
curl -s -X POST $API/walker/StartRun -H "Content-Type: application/json" \
-d "{\"hid\":\"$h\",\"seed\":1,\"framings\":[],\"framing_source\":\"bundled\",\"provider\":\"fireworks\"}" >/dev/null
curl -s -X POST $API/walker/RunToEnd -H "Content-Type: application/json" -d '{}' >/dev/null
done
Verify with score_source, which returns "fireworks" when warm, "keyword" when cold, and
"fireworks (partial: 31/40)" when a response was incomplete — a legitimate state, not an error.
For a fully offline model: ollama serve, then import the GGUF that jac model pull cached
(byLLM's own local: scheme needs llama-cpp-python, which will not build on arm64):
printf 'FROM ~/.cache/jac/models/gemma-4-e4b/gemma-4-E4B-it-Q4_K_M.gguf\nPARAMETER temperature 0.2\nPARAMETER num_ctx 4096\n' > Modelfile
ollama create gemma-4-e4b -f Modelfile
Twenty-five tests in pheromone/swarm.test.jac, an annex of swarm.sv.jac. Golden replay is the load-bearing one: three full runs at the same seed must produce identical receipt streams, and a second test asserts a different seed produces a different stream so that "identical" cannot pass vacuously. The rest cover evaporation floor clamping, deposit cap clamping, canonical reduce ordering, the forced-tie tiebreak, trail suppression on already-read destinations, trail extraction terminating on cyclic graphs, suppressed fragments being excluded from the extracted corridor, and state being rebuilt from stored state after a restart.
Every test was verified to go red when the behaviour it covers is broken — see build-plans/SPRINT1-A-engine-HANDOVER.md for the tamper table.
Three distinct byLLM calls, all in pheromone/framing.sv.jac, and only the middle one is
load-bearing:
def llm_framings(...) -> FramingTriple by scorer(). Three search
angles on the selected hypothesis. Falls back to a text-mode variant for small local
models that cannot emit a typed object, then to bundled framings.def score_corpus(...) -> FragmentScoreList by scorer(temperature=0.0).
One call per framing scores all forty fragments with a rationale each, chunked at twenty
to fit small local models. This is where the model does real work: it decides what
every fragment means against every framing, which drives every downstream decision.def llm_corridor_story(...) -> CorridorStory by scorer(). Reads
the extracted corridor back as causal prose citing fragment ids, and names its own weakest
link. Off the critical path entirely.The search itself never calls a model. Scores are cached to the graph as ScoreCache
nodes keyed by sha256(provider | model | framing | fragment), so the swarm reads only the
cache. Three calls, about ten seconds, then it runs offline forever. cached_relevance never
raises: a missing key, a timeout, a malformed response, or an invented fragment id each leave
that fragment on the deterministic stemmed-keyword floor.
Providers: Gemini, Claude, OpenAI, Fireworks, local Ollama, mock, and the keyword floor. A missing key is a state, never an exception.
Every number below is jac run eval.jac on this repo — 3 hypotheses × 20 seeds = 60 trials
per column. Reproduce them with the commands under the table.
Metric: trail precision — of the fragments in the extracted corridor, what fraction lie on the planted causal chain. Three modes, same total budget:
| keyword scorer | model scorer (Fireworks gpt-oss-120b) | |
|---|---|---|
| swarm | 0.494 | 0.472 |
| trio (no trails) | 0.431 | 0.489 |
| solo (equal budget) | 0.392 | 0.242 |
| swarm − solo | +0.103 | +0.231 |
| swarm − trio | +0.064 | −0.017 |
| win / tie / loss vs solo | 23 / 32 / 5 | 45 / 13 / 2 |
| same seed replays identically | yes | yes |
| mean unique fragments visited | 6.7 / 40 | 7.7 / 40 |
The keyword column is post-Commons, after a saturation fix described below. The model column was measured before that fix; re-measurement is in progress and the delta is expected to hold or improve.
What we claim: with semantic scoring, the swarm beats a single walker of equal budget by +0.231 trail precision, 45 wins to 2 across 60 trials, and every run replays byte-identically. On the deterministic keyword scorer the margin is +0.103, 23 wins to 5.
Why the second column matters. With a keyword scorer the advantage is +0.103. The gap nearly triples once a model decides what each fragment means — semantic scoring collapses the solo walker to 0.242 while the swarm holds 0.472. Coordination only pays off when agents can tell relevant evidence from merely word-matching evidence. That is why the LLM is on the critical path rather than decorating the edges.
The number that goes against us. swarm − trio — turning trail-reading off — is only
+0.064 on keyword and −0.017 under model scoring. Within a single eight-tick run the
agents barely cross paths, so there is little trail to read. Stigmergy is a many-passes
mechanism and we are measuring one pass. We report it because a mechanism that measures
near-null on its own ablation is the first thing a reader should be told.
Commons accumulates trails across runs: what one analyst's search deposits persists and
informs the next. It is verified working — commons_version increments per run and every
contribution is recorded with provenance.
It is deliberately excluded from the numbers above, and that exclusion is itself a fix.
Because the sweep runs 60 trials against one accumulating graph, commons saturated within
about nine runs: values reached 3× COMMONS_REF, norm_commons clamped them all to 1.0, and
W_COMMONS × 1.0 was then added identically to swarm, trio and solo. A constant cannot
discriminate, and the measured delta collapsed to −0.014. Zeroing commons inside the baseline
comparison restored it to +0.103 — better than before the layer existed — and cut the sweep
from twenty minutes to under four.
The honest statement: commons is a cross-run feature and the table measures within-run coordination. Evaluating it properly needs a multi-session benchmark we did not have time to build.
Coverage is the known weakness. Agents visit 6.7 of 40 fragments. Exploration is swamped:
the relevance term reaches ~0.8 while W_EXPLORE is 0.30, so all five agents converge on the
same high-scoring fragments instead of spreading. Raising W_EXPLORE is the obvious lever; we
did not have measured evidence in time to justify changing a constant everything else was
tuned against.
cd pheromone && set -a && source .env && set +a
jac clean --data && jac run eval.jac 2>&1 | grep -E "^(H[0-9]|OVERALL|RECORD|REPLAY)"
jac clean --data && EVAL_PROVIDER=fireworks jac run eval.jac 2>&1 | grep -E "^(H[0-9]|OVERALL|RECORD|REPLAY)"
Given a fixed score cache, the search is a pure function: three consecutive runs at one seed
return byte-identical corridors, and jac test asserts identical receipt streams across three
runs.
The cache is the boundary. Regenerating it can shift the corridor, because model scoring is
not bit-reproducible even at temperature=0.0. The guarantee is deterministic given a cache,
not deterministic across cache rebuilds. The cache lives on the graph, so it survives
restarts and never silently regenerates.
This ranks path strength, not truth probability. A thick corridor means many agents found that route useful under their framings. It does not mean the claim is true, and the system does not validate the underlying evidence.
The corpus is curated. The forty fragments are fiction we wrote, with three causal chains
planted deliberately. trail_precision is measurable because we planted them, so the metric
rewards finding structure we put there. On real evidence there is no planted chain and no
precision number.
Precision penalises the most interesting result. It counts only the selected hypothesis's
chain. On H2 seed 1 the corridor runs e08 → e02 → e31 → e21 → e03 → e11 — from the firmware chain,
through bridge fragment e31, into the power chain. That crossing is the most valuable thing
the system found, and the metric scores it as misses.
Five agents, eight ticks, forty fragments. Nothing has been tested at a scale where the graph does not fit in memory, and the reducer is a single writer by design, so throughput is bounded to one tick at a time.
The model can be wrong and the map will not tell you. Scores are cached with a per-fragment rationale and you can read them, but a confidently mis-scored fragment produces a confident-looking corridor. The keyword scorer stays available as a deterministic floor precisely so results can be compared against something with no opinions.
Not evaluated against a human baseline. We do not know whether an analyst given the same forty fragments and twenty minutes would do better.
Aarnav Gutti — engine, tests, evaluation harness. Tirth Subawalla — interface, canvas, motion. Harshith Sai Mannaru — corpus, model layer, integration, demo.
Built at JacHacks SF, 26 July 2026. All case content is fictional; no real organisation, telemetry, or reporting is depicted.
52 commits
13 commits
Hacker News (1)
Jac
84.1%
CSS
10.1%
HTML
4.7%
Shell
1.0%
Ants have no manager. An ant wanders, finds food, and dribbles a chemical on the way home; other ants prefer strong-smelling ground, so good paths reinforce themselves and bad ones evaporate. Pheromone Trails points that mechanism at research: five agents walk a graph of forty evidence fragments looking for evidence that bears on a question, and strengthen the connections that paid off. The output is a map, not a summary — strong connections thick and bright, dead ends faded — and you click the brightest corridor to read the actual multi-fragment chain with every source and score exposed.
The corpus is the graph and the graph is the data: forty Evidence nodes joined by EvidenceLink edges that carry their own mutable state — kind, relevance, and a pheromone value that rises when an agent finds a path useful and decays every tick. Coordination happens entirely through that shared edge state; no agent sends a message or knows another exists.
The property that makes five concurrent agents testable is a propose/reduce split. ForageTick spawns on an agent's current fragment, scores every incident edge, and returns exactly one Proposal — it writes nothing. ReduceTick is the single writer: it sorts proposals into a canonical order (fid|dst_eid, never arrival order), applies deposits clamped to PHER_CAP, runs one global evaporation pass clamped to PHER_FLOOR, and records a Receipt. Exploration randomness is sha256(seed | tick | agent | candidate) rather than a PRNG stream, because Python's hash() is salted per process and a shared random.Random makes each draw depend on the order agents were iterated. The result is that the same seed reproduces byte-identical receipts, which is what the replay scrubber and the integrity Warden both rest on. Trail terms are forced to zero for fragments an agent has already read — a trail is a tip from someone else, never a reason to re-read your own notes. All state is root-reachable, so it persists across a process restart with no database and no serialisation code.
Requires Jac 0.34.6 exactly (pip install jaclang gives a different generation of the language):
curl -fsSL https://raw.githubusercontent.com/jaseci-labs/jaseci/main/scripts/install.sh | bash -s -- --version 0.34.6
cd pheromone
jac install byllm
jac start main.jac # serve the app -> http://localhost:8000
# jac run # dev mode with hot reload; spawns a separate vite child
jac clean --data && jac test # engine suite, 25 tests
jac run eval.jac # evaluation sweep, 3 hypotheses x 20 seeds
jac run corpus_check.jac # validates every corpus invariant
jac run framing_check.jac # validates the scorer
jac run golden_path_check.jac # end-to-end acceptance
jac clean --data before a test run is not optional: jac run persists into .jac/, and stale anchors meeting recompiled archetypes produce NodeAnchor ... is not a valid reference!.
Two operational notes that cost us hours:
jac start and jac run spawn processes that pkill -f "jac start" does not fully
match. An orphaned server keeps its port and the new one silently takes the next, so you
end up talking to a stale process. Kill jac start, vite.js and jac run, then confirm
exactly one listener before trusting anything..sv.jac change and can fail the non-dev rollup
build with errors that make no sense ("BottomStrip" is not exported while jac check is
clean). Fix: rm -rf .jac/client/compiled .jac/client/.vite .jac/cache — never
.jac/data, which holds the warmed score cache.Eleven walker:pub endpoints — LoadCase · GetState · ResetCase · StartRun · StepRun · RunToEnd · ToggleFragment · RunBaseline · ReplayTo (in swarm.sv.jac) and RunWarden · InjectPoison (in warden.sv.jac).
Plus nine def:pub functions: list_providers · set_provider · propose_framings · ensure_scores · score_source · narrate_corridor · audit_sessions · concentration · restart_server.
Everything runs with no API key at all — the deterministic stemmed-keyword scorer is always available and is what the golden-path demo uses. A model is an upgrade, not a dependency.
Create pheromone/.env (gitignored):
FIREWORKS_AI_API_KEY=fw_your_key
FIREWORKS_SLUG=accounts/fireworks/models/gpt-oss-120b
OLLAMA_MODEL=gemma-4-e4b # optional, fully offline
GOOGLE_API_KEY= # optional
ANTHROPIC_API_KEY= # optional
FIREWORKS_SLUG is the bare slug — the code prepends fireworks_ai/. List what your key
can reach rather than guessing:
curl -s https://api.fireworks.ai/inference/v1/models \
-H "Authorization: Bearer $FIREWORKS_AI_API_KEY" \
| python3 -c "import sys,json;[print(m['id']) for m in json.load(sys.stdin)['data']]"
Then export the env before launching the server — the server reads the process
environment, so .env alone is not enough:
set -a && source .env && set +a && jac start main.jac
Warm the cache before you rely on it. The first model-scored run makes three batched calls per hypothesis and takes roughly a minute; every run after that is instant because the scores live on the graph. Warming must go through the API — a CLI script warms a different root:
API=http://localhost:8000
for h in H1 H2 H3; do
curl -s -X POST $API/walker/ResetCase -H "Content-Type: application/json" -d '{}' >/dev/null
curl -s -X POST $API/walker/StartRun -H "Content-Type: application/json" \
-d "{\"hid\":\"$h\",\"seed\":1,\"framings\":[],\"framing_source\":\"bundled\",\"provider\":\"fireworks\"}" >/dev/null
curl -s -X POST $API/walker/RunToEnd -H "Content-Type: application/json" -d '{}' >/dev/null
done
Verify with score_source, which returns "fireworks" when warm, "keyword" when cold, and
"fireworks (partial: 31/40)" when a response was incomplete — a legitimate state, not an error.
For a fully offline model: ollama serve, then import the GGUF that jac model pull cached
(byLLM's own local: scheme needs llama-cpp-python, which will not build on arm64):
printf 'FROM ~/.cache/jac/models/gemma-4-e4b/gemma-4-E4B-it-Q4_K_M.gguf\nPARAMETER temperature 0.2\nPARAMETER num_ctx 4096\n' > Modelfile
ollama create gemma-4-e4b -f Modelfile
Twenty-five tests in pheromone/swarm.test.jac, an annex of swarm.sv.jac. Golden replay is the load-bearing one: three full runs at the same seed must produce identical receipt streams, and a second test asserts a different seed produces a different stream so that "identical" cannot pass vacuously. The rest cover evaporation floor clamping, deposit cap clamping, canonical reduce ordering, the forced-tie tiebreak, trail suppression on already-read destinations, trail extraction terminating on cyclic graphs, suppressed fragments being excluded from the extracted corridor, and state being rebuilt from stored state after a restart.
Every test was verified to go red when the behaviour it covers is broken — see build-plans/SPRINT1-A-engine-HANDOVER.md for the tamper table.
Three distinct byLLM calls, all in pheromone/framing.sv.jac, and only the middle one is
load-bearing:
def llm_framings(...) -> FramingTriple by scorer(). Three search
angles on the selected hypothesis. Falls back to a text-mode variant for small local
models that cannot emit a typed object, then to bundled framings.def score_corpus(...) -> FragmentScoreList by scorer(temperature=0.0).
One call per framing scores all forty fragments with a rationale each, chunked at twenty
to fit small local models. This is where the model does real work: it decides what
every fragment means against every framing, which drives every downstream decision.def llm_corridor_story(...) -> CorridorStory by scorer(). Reads
the extracted corridor back as causal prose citing fragment ids, and names its own weakest
link. Off the critical path entirely.The search itself never calls a model. Scores are cached to the graph as ScoreCache
nodes keyed by sha256(provider | model | framing | fragment), so the swarm reads only the
cache. Three calls, about ten seconds, then it runs offline forever. cached_relevance never
raises: a missing key, a timeout, a malformed response, or an invented fragment id each leave
that fragment on the deterministic stemmed-keyword floor.
Providers: Gemini, Claude, OpenAI, Fireworks, local Ollama, mock, and the keyword floor. A missing key is a state, never an exception.
Every number below is jac run eval.jac on this repo — 3 hypotheses × 20 seeds = 60 trials
per column. Reproduce them with the commands under the table.
Metric: trail precision — of the fragments in the extracted corridor, what fraction lie on the planted causal chain. Three modes, same total budget:
| keyword scorer | model scorer (Fireworks gpt-oss-120b) | |
|---|---|---|
| swarm | 0.494 | 0.472 |
| trio (no trails) | 0.431 | 0.489 |
| solo (equal budget) | 0.392 | 0.242 |
| swarm − solo | +0.103 | +0.231 |
| swarm − trio | +0.064 | −0.017 |
| win / tie / loss vs solo | 23 / 32 / 5 | 45 / 13 / 2 |
| same seed replays identically | yes | yes |
| mean unique fragments visited | 6.7 / 40 | 7.7 / 40 |
The keyword column is post-Commons, after a saturation fix described below. The model column was measured before that fix; re-measurement is in progress and the delta is expected to hold or improve.
What we claim: with semantic scoring, the swarm beats a single walker of equal budget by +0.231 trail precision, 45 wins to 2 across 60 trials, and every run replays byte-identically. On the deterministic keyword scorer the margin is +0.103, 23 wins to 5.
Why the second column matters. With a keyword scorer the advantage is +0.103. The gap nearly triples once a model decides what each fragment means — semantic scoring collapses the solo walker to 0.242 while the swarm holds 0.472. Coordination only pays off when agents can tell relevant evidence from merely word-matching evidence. That is why the LLM is on the critical path rather than decorating the edges.
The number that goes against us. swarm − trio — turning trail-reading off — is only
+0.064 on keyword and −0.017 under model scoring. Within a single eight-tick run the
agents barely cross paths, so there is little trail to read. Stigmergy is a many-passes
mechanism and we are measuring one pass. We report it because a mechanism that measures
near-null on its own ablation is the first thing a reader should be told.
Commons accumulates trails across runs: what one analyst's search deposits persists and
informs the next. It is verified working — commons_version increments per run and every
contribution is recorded with provenance.
It is deliberately excluded from the numbers above, and that exclusion is itself a fix.
Because the sweep runs 60 trials against one accumulating graph, commons saturated within
about nine runs: values reached 3× COMMONS_REF, norm_commons clamped them all to 1.0, and
W_COMMONS × 1.0 was then added identically to swarm, trio and solo. A constant cannot
discriminate, and the measured delta collapsed to −0.014. Zeroing commons inside the baseline
comparison restored it to +0.103 — better than before the layer existed — and cut the sweep
from twenty minutes to under four.
The honest statement: commons is a cross-run feature and the table measures within-run coordination. Evaluating it properly needs a multi-session benchmark we did not have time to build.
Coverage is the known weakness. Agents visit 6.7 of 40 fragments. Exploration is swamped:
the relevance term reaches ~0.8 while W_EXPLORE is 0.30, so all five agents converge on the
same high-scoring fragments instead of spreading. Raising W_EXPLORE is the obvious lever; we
did not have measured evidence in time to justify changing a constant everything else was
tuned against.
cd pheromone && set -a && source .env && set +a
jac clean --data && jac run eval.jac 2>&1 | grep -E "^(H[0-9]|OVERALL|RECORD|REPLAY)"
jac clean --data && EVAL_PROVIDER=fireworks jac run eval.jac 2>&1 | grep -E "^(H[0-9]|OVERALL|RECORD|REPLAY)"
Given a fixed score cache, the search is a pure function: three consecutive runs at one seed
return byte-identical corridors, and jac test asserts identical receipt streams across three
runs.
The cache is the boundary. Regenerating it can shift the corridor, because model scoring is
not bit-reproducible even at temperature=0.0. The guarantee is deterministic given a cache,
not deterministic across cache rebuilds. The cache lives on the graph, so it survives
restarts and never silently regenerates.
This ranks path strength, not truth probability. A thick corridor means many agents found that route useful under their framings. It does not mean the claim is true, and the system does not validate the underlying evidence.
The corpus is curated. The forty fragments are fiction we wrote, with three causal chains
planted deliberately. trail_precision is measurable because we planted them, so the metric
rewards finding structure we put there. On real evidence there is no planted chain and no
precision number.
Precision penalises the most interesting result. It counts only the selected hypothesis's
chain. On H2 seed 1 the corridor runs e08 → e02 → e31 → e21 → e03 → e11 — from the firmware chain,
through bridge fragment e31, into the power chain. That crossing is the most valuable thing
the system found, and the metric scores it as misses.
Five agents, eight ticks, forty fragments. Nothing has been tested at a scale where the graph does not fit in memory, and the reducer is a single writer by design, so throughput is bounded to one tick at a time.
The model can be wrong and the map will not tell you. Scores are cached with a per-fragment rationale and you can read them, but a confidently mis-scored fragment produces a confident-looking corridor. The keyword scorer stays available as a deterministic floor precisely so results can be compared against something with no opinions.
Not evaluated against a human baseline. We do not know whether an analyst given the same forty fragments and twenty minutes would do better.
Aarnav Gutti — engine, tests, evaluation harness. Tirth Subawalla — interface, canvas, motion. Harshith Sai Mannaru — corpus, model layer, integration, demo.
Built at JacHacks SF, 26 July 2026. All case content is fictional; no real organisation, telemetry, or reporting is depicted.
Hacker News (1)
52 commits
13 commits
Jac
84.1%
CSS
10.1%
HTML
4.7%
Shell
1.0%