zettelkasten.synapse.navigation¶
zettelkasten.synapse.navigation ¶
The navigation engine of enforced epistemic honesty.
This is the STEERING layer of the synapse stack. It closes the loop between an
LLM that reasons freely and a deterministic engine that owns every ground
truth: the LLM chooses navigation actions and emits a cited reasoning-DAG, but a
DETERMINISTIC executor here feeds it retrieval, enforces the token budget, and
delegates the final ANSWER-vs-ABSTAIN verdict to
:mod:zettelkasten.synapse.grounding_router. Because the navigator never decides
grounding itself, fabrication is structurally impossible and abstention is
automatic whenever coverage or grounding breaks.
It integrates the four upstream synapse workstreams (read in full before editing
this module): the MemDSL contract (:mod:zettelkasten.synapse.memdsl.schema /
.parser), the substrate assembler (:mod:zettelkasten.synapse.substrate),
the deterministic epistemics engine (:mod:zettelkasten.synapse.epistemics), and
the grounding router (:mod:zettelkasten.synapse.grounding_router). It EMITS and
CONSUMES the contract vocabulary rather than inventing a parallel one.
The pieces, in the order the executor uses them:
-
Deterministic action executor + action grammar. The v1 read actions —
expand/join/ground/prune/abstain/answer— are exactly the ones registered in :data:zettelkasten.synapse.memdsl.schema.ACTION_REGISTRY. The executor runs the LLM's non-terminal actions in a FIXED, deterministic order (topologic expand → join, then parametric ground, then prune), then handles the single terminal decision. All I/O (the LLM, tether verification, verifier grounding, neighborhood expansion, the decision-tree walk) is injected, so the executor is a pure function of(inputs, seams)and runs in tests with no model or store. -
Injectable, advisory VOI. The production default remains :func:
minimal_voi(relevance × status_gap); the deterministic :func:resolvability_cost_voipolicy additionally divides by bounded token cost. VOI only ranks bounded prompt annotations and labels fallback diagnostics. It never executes an action or weakens the hard token/hop guardrails. -
Bounded self-correction. A verifier failure (a router
false-premiseabstain) triggers a RE-NAVIGATE — the executor feeds the failure back and lets the LLM pick a different path rather than doubling down — capped at :attr:NavConfig.max_renavigations. -
Sequential multi-hop state. Cross-type
joinmoves record :class:BridgeBindings carrying the WEAKEST-LINK status across the joined endpoints, and a later hop resting on a binding is capped by it — a hop can be no more grounded than the binding it rests on. -
Model-agnostic LLM seam. The LLM is reached through :func:
zettelkasten.llm_adapter.run_toolless_agent(wrapped by :func:default_llm), but the executor only ever calls the injected :data:LLMcallable, so a test injects a scripted stub with no real model call. The LLM's structured output IS the MemDSL OUTPUT contract, parsed by :func:zettelkasten.synapse.memdsl.parser.parse_output; a parse failure gets ONE repair retry (the validator error is fed back) before the turn abstains.
VOICandidate
dataclass
¶
One handle the VOI model scores: a rendered node plus its derived status.
relevance is the retrieval relevance of the node (a [0, 1]-ish score;
the v1 default is a flat 1.0 for every present node — a richer relevance
channel is an injected map). status is the node's deterministically
derived :class:~zettelkasten.synapse.epistemics.StatusVerdict. cost is
an estimated token cost of grounding/expanding the handle. A Fang-2026
resolvability/cost VOI reads the SAME fields, so it drops in without a
signature change.
Source code in zettelkasten/synapse/navigation.py
VOIAnnotation
dataclass
¶
The advisory VOI annotation attached to a candidate handle.
voi is the (advisory) value-of-information estimate; cost the
estimated token cost. The executor uses these only for bounded prompt hints
and to label a structural fallback; the hard budget guardrail is separate.
Source code in zettelkasten/synapse/navigation.py
BridgeBinding
dataclass
¶
A cross-hop binding: an entity that bridges two hops, with weakest status.
A join across type records the bridge entity (the shared node), the
hop_from / hop_to node tokens it links, and the WEAKEST-LINK
:class:~zettelkasten.synapse.epistemics.StatusVerdict over the two endpoints
(composed with :func:zettelkasten.synapse.epistemics.propagate, so tier is
capped by the weaker endpoint and taint is unioned). The executor then caps
the hop_to node's status by this binding when routing — a later hop can be
no more grounded than the binding it rests on.
Source code in zettelkasten/synapse/navigation.py
NavConfig
dataclass
¶
Deterministic bounds on a navigation run.
token_budget is the HARD guardrail: the executor halts the loop (with a
VOI-max fallback) once it can no longer afford the next turn. The default of
8000 is deliberately generous: NAV_SYSTEM now carries TWO
@memdsl.out/1 exemplars (answer + abstain), so the old 4000 floor was
too tight to reliably reach the model on a non-trivial neighborhood.
max_hops caps the number of LLM turns; max_renavigations caps
verifier-failure re-navigations; max_parse_repairs caps structured-output
repair retries. trace_floor is forwarded to the router's prose gate.
max_nodes is the hard ceiling on the neighborhood size — LLM-driven
expand/ground merges never grow it past this — mirroring the substrate
:class:~zettelkasten.synapse.substrate.Budget.max_nodes; None (the
default) leaves in-loop growth unbounded (only the token budget bounds it).
render_max_body_chars caps how many characters of each node body are
SHOWN in the render: None shows the full body (no elision), a positive int
truncates a long body to a lossy view (the render then emits an elided gap
and a ground fetch-back handle, so the agent can re-fetch the FULL body).
It bounds per-node body DEPTH, NOT neighborhood WIDTH: a wide hub (a node with
many neighbors) is bounded by token_budget / max_nodes, never by this
knob — trimming each body shorter does not reduce the NUMBER of nodes shown.
Elision only bounds the PROMPT — the grounder re-fetches and the grounding
gate always verifies against the full body — so it never weakens grounding; it
only keeps a body-heavy neighborhood from blowing the token budget BEFORE the
model is ever called. The default of 600 keeps the budget guardrail from
halting on a body-heavy neighborhood.
voi_policy selects the deterministic advisory scorer. The shipped default
remains "minimal" until benchmark evidence justifies promotion;
"resolvability_cost" activates :func:resolvability_cost_voi.
focus_handle_limit bounds the number of top-scoring handles shown as
prompt hints. Neither setting executes or suppresses an action.
Source code in zettelkasten/synapse/navigation.py
NavResult
dataclass
¶
The outcome of a navigation run.
decision is the router's (or a structurally-forced) ANSWER/ABSTAIN
verdict; halted_reason names why the loop ended (answer / abstain
/ budget / hops / parse-fail); loop_break is "voi-max"
when a budget/hop guardrail broke a loop, else empty. The remaining fields
expose the run's telemetry for inspection and testing.
Source code in zettelkasten/synapse/navigation.py
Navigator ¶
The deterministic action executor that steers the LLM to ANSWER or ABSTAIN.
Construct with the injected seams (all optional except the LLM) and call
:meth:navigate. Every source of non-determinism — the model, tether
verification, verifier grounding, neighborhood expansion, and the prune walk
— is injected, so a run is reproducible and testable with no live model or
store. The navigator NEVER decides grounding itself: the terminal ANSWER/
ABSTAIN verdict is always delegated to
:func:zettelkasten.synapse.grounding_router.route.
Source code in zettelkasten/synapse/navigation.py
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 | |
navigate ¶
navigate(question: str, neighborhood: Neighborhood, *, groundings: Mapping[str, GroundingSpec] | None = None) -> NavResult
Run the navigation loop over neighborhood to a verdict.
question is the user's query; neighborhood is the assembled
substrate the run starts from (further expand moves may grow it).
groundings optionally seeds grounding specs (the ground action
adds more). Returns a :class:NavResult whose decision is the
router's ANSWER/ABSTAIN verdict (or a structurally-forced ABSTAIN on a
budget/parse failure).
Source code in zettelkasten/synapse/navigation.py
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 | |
default_llm ¶
Build the production :data:LLM seam over :func:run_toolless_agent.
model keeps the navigator model-agnostic at its own API boundary and is
forwarded to :func:zettelkasten.llm_adapter.run_toolless_agent, which passes
it to DashboardAgent's supported model option. It also remains in the
agent name for traceability. timeout bounds the call.
Source code in zettelkasten/synapse/navigation.py
status_gap ¶
status_gap(verdict: StatusVerdict) -> float
How much a node's status LIMITS the answer, in [0, 1].
The gap is what grounding a node could still buy: a clean VERIFIED node
(grounded / measured with no taint) is already maximally useful, so
its gap is 0.0 (grounding it further has no value); an inferred node,
or any node carrying stale / unresolved / contested taint, has a
full gap of 1.0 (resolving it is where the value is).
Source code in zettelkasten/synapse/navigation.py
minimal_voi ¶
minimal_voi(candidate: VOICandidate) -> VOIAnnotation
The v1-minimal, advisory VOI: voi ≈ relevance × status_gap.
Deliberately simple — relevance weights how on-topic a handle is and the
status gap weights how much its inferred/tainted status still limits the
answer, so the highest-VOI handle is the most relevant not-yet-grounded node.
This is the placeholder behind :data:VOIModel: the full Fang-2026
resolvability/cost-ratio VOI slots in as a different callable of the same
shape with no change here or in the executor.
Source code in zettelkasten/synapse/navigation.py
resolvability_cost_voi ¶
resolvability_cost_voi(candidate: VOICandidate) -> VOIAnnotation
Cost-aware advisory VOI over deterministic evidence.
Relevance is clamped to [0, 1]; status gap is router-owned and monotonic;
token cost is bounded to [1, 100_000] before division. A clean verified
node therefore remains exactly zero, while increasing relevance or reducing
cost can never lower a non-zero score. The annotation is advisory only.