Skip to content

zettelkasten.synapse.navigate

zettelkasten.synapse.navigate

Out-of-process synapse grounded-navigation loop (subprocess / CLI entrypoint).

The synapse navigate action steers an LLM through a knowledge substrate via the in-process cursor-sdk agent bridge (:func:zettelkasten.llm_adapter.run_toolless_agent). That bridge does NOT come up inside the stdio MCP server — whose stdin/stdout ARE the JSON-RPC transport — nor inside the warm search/frame worker, which owns its own stdin/stdout for a line protocol: in either host the bridge launches but its completion never returns, hanging the process. Neighborhood assembly ALSO fans out to native kglite embedding indexes (including always-cold federated peers) whose first-access indexing has been observed to SIGSEGV, which would take the stdio MCP server down with it.

Running the loop in a FRESH subprocess fixes both, exactly as :mod:zettelkasten.synapse.build does for the typing bridge: a clean host with ordinary stdio lets the agent bridge launch as it does for the dashboard/CLI, a native crash kills only the child (the parent maps a killing signal to a clean NativeCrash error), and a slow cold assembly is bounded by our own timeout rather than the client's. :mod:faulthandler is enabled so a SIGSEGV dumps the Python traceback (which in-scope source / native call it died in) to stderr, which the parent captures — turning an opaque exit 139 into a located fault.

Usage::

python -m zettelkasten.synapse.navigate --payload-json '{"question": "...", ...}'

--payload-json is a JSON object with keys: question (required), projects (null = configured intersection, [] = memory only, or a token list), model (optional requested model id), max_nodes (-1 = the default cap, 0 = no caller-specified count cap while retaining the token-derived pre-assembly safety ceiling, >0 = an explicit hard cap), plus hops / trace_floor / token_budget / render_max_body_chars (each < 0 = use the substrate/router/navigator default; render_max_body_chars == 0 disables body elision). Prints one SYNAPSE_NAVIGATE_RESULT <json> line to stdout (logs + any fault trace go to stderr). The result JSON is the navigation verdict payload. Every accepted answer carries the versioned answer_surface projection; every abstention/failure carries answer_surface: null. A Python-level assembly/model/nav failure degrades to a clean labeled abstain here (returncode 0); only an uncatchable native crash exits non-zero for the parent to map.

NavigationValidationError

Bases: ValueError

A public navigation resource parameter is outside its supported domain.

Source code in zettelkasten/synapse/navigate.py
class NavigationValidationError(ValueError):
    """A public navigation resource parameter is outside its supported domain."""

NavDetail dataclass

The INTERNAL, benchmark-only detailed outcome of a navigation run.

This is the diagnostics seam behind :func:run_navigate: it pairs the EXACT public verdict dict (:attr:public — byte-for-byte the same keys/values the MCP handler, CLI, and :func:run_navigate ship) with the underlying :class:~zettelkasten.synapse.navigation.NavResult and a wall-clock :attr:elapsed_s so an in-process caller (the MemDSL Navigation Truth Benchmark) can inspect what the public payload deliberately hides: the parsed MemDSL :class:~zettelkasten.synapse.memdsl.schema.AgentOutput, parse-repair count, the full navigation trace, and the router's per-node / per-claim verdicts, honesty, and traceability reports (all carried on nav_result.decision).

:attr:nav_result is None on the labeled-abstain failure path (assembly / model / navigation raised): there is no run to inspect, so the benchmark reads the labeled-abstain :attr:public payload and the :attr:error string alone. :attr:public is ALWAYS populated (the honesty contract holds even when the substrate is unavailable), so a benchmark can rely on it unconditionally.

This type is NOT part of the shipped MCP/CLI/run_navigate contract — it is an internal seam. :func:run_navigate returns only :attr:public, keeping the public schema untouched.

Source code in zettelkasten/synapse/navigate.py
@dataclass
class NavDetail:
    """The INTERNAL, benchmark-only detailed outcome of a navigation run.

    This is the diagnostics seam behind :func:`run_navigate`: it pairs the EXACT
    public verdict dict (:attr:`public` — byte-for-byte the same keys/values the
    MCP handler, CLI, and :func:`run_navigate` ship) with the underlying
    :class:`~zettelkasten.synapse.navigation.NavResult` and a wall-clock
    :attr:`elapsed_s` so an in-process caller (the MemDSL Navigation Truth
    Benchmark) can inspect what the public payload deliberately hides: the parsed
    MemDSL :class:`~zettelkasten.synapse.memdsl.schema.AgentOutput`, parse-repair
    count, the full navigation ``trace``, and the router's per-node / per-claim
    verdicts, honesty, and traceability reports (all carried on
    ``nav_result.decision``).

    :attr:`nav_result` is ``None`` on the labeled-abstain failure path (assembly /
    model / navigation raised): there is no run to inspect, so the benchmark reads
    the labeled-abstain :attr:`public` payload and the :attr:`error` string alone.
    :attr:`public` is ALWAYS populated (the honesty contract holds even when the
    substrate is unavailable), so a benchmark can rely on it unconditionally.

    This type is NOT part of the shipped MCP/CLI/``run_navigate`` contract — it is
    an internal seam. :func:`run_navigate` returns only :attr:`public`, keeping
    the public schema untouched.
    """

    public: dict[str, Any]
    elapsed_s: float
    nav_result: "NavResult | None" = None
    error: str | None = None

validate_navigation_parameters

validate_navigation_parameters(*, max_nodes: Any = -1, hops: Any = -1, token_budget: Any = -1, render_max_body_chars: Any = -1) -> dict[str, int]

Return validated public resource knobs or raise before any store/model work.

Source code in zettelkasten/synapse/navigate.py
def validate_navigation_parameters(
    *,
    max_nodes: Any = -1,
    hops: Any = -1,
    token_budget: Any = -1,
    render_max_body_chars: Any = -1,
) -> dict[str, int]:
    """Return validated public resource knobs or raise before any store/model work."""

    return {
        "max_nodes": _bounded_public_int(
            "max_nodes", max_nodes, minimum=0, maximum=MAX_PUBLIC_NODES
        ),
        "hops": _bounded_public_int(
            "hops", hops, minimum=0, maximum=MAX_PUBLIC_HOPS
        ),
        "token_budget": _bounded_public_int(
            "token_budget",
            token_budget,
            minimum=MIN_PUBLIC_TOKEN_BUDGET,
            maximum=MAX_PUBLIC_TOKEN_BUDGET,
        ),
        "render_max_body_chars": _bounded_public_int(
            "render_max_body_chars",
            render_max_body_chars,
            minimum=0,
            maximum=MAX_PUBLIC_BODY_CHARS,
        ),
    }

validation_abstention

validation_abstention(detail: str) -> dict[str, Any]

Build the labeled public abstention returned for invalid resource inputs.

Source code in zettelkasten/synapse/navigate.py
def validation_abstention(detail: str) -> dict[str, Any]:
    """Build the labeled public abstention returned for invalid resource inputs."""

    return {
        "verdict": "abstain",
        "status": None,
        "reasons": ["navigator-unavailable"],
        "halted_reason": "validation",
        "conclusion": "",
        "outdated": False,
        "answer_surface": None,
        "trace_surface": None,
        "envelope": None,
        "hops": 0,
        "renavigations": 0,
        "tokens_spent": 0,
        "error": detail,
        "type": "ValidationError",
    }

run_navigate

run_navigate(question: str, *, projects: list[str] | None = None, max_nodes: int = -1, hops: int = -1, trace_floor: float = -1.0, token_budget: int = -1, render_max_body_chars: int = -1, model: str = '', llm: 'Callable[[str, str], str] | None' = None) -> dict[str, Any]

Run the grounded cross-store navigation loop and return its verdict dict.

The stable PUBLIC entrypoint shared by the MCP handler (via the subprocess launcher) and this module's CLI. It is a thin wrapper over the internal :func:_run_navigate_detailed seam, returning ONLY the public verdict dict — the exact keys/semantics the shipped MCP/CLI schema depends on. Accepted answers add a nested, versioned answer_surface derived deterministically from the parsed DAG, router decision, and final envelope; abstain/failure results carry answer_surface=None. A projection inconsistency degrades to a labeled abstain. The diagnostics the benchmark needs live on :class:NavDetail behind that seam and never leak into this dict.

See :func:_run_navigate_detailed for parameter semantics (especially max_nodes=-1 default / 0 no caller count cap but token-bounded assembly / >0 explicit hard cap), requested model forwarding, the injectable llm, and labeled-abstain degradation on any assembly/model/navigation failure.

Source code in zettelkasten/synapse/navigate.py
def run_navigate(
    question: str,
    *,
    projects: list[str] | None = None,
    max_nodes: int = -1,
    hops: int = -1,
    trace_floor: float = -1.0,
    token_budget: int = -1,
    render_max_body_chars: int = -1,
    model: str = "",
    llm: "Callable[[str, str], str] | None" = None,
) -> dict[str, Any]:
    """Run the grounded cross-store navigation loop and return its verdict dict.

    The stable PUBLIC entrypoint shared by the MCP handler (via the subprocess
    launcher) and this module's CLI. It is a thin wrapper over the internal
    :func:`_run_navigate_detailed` seam, returning ONLY the public verdict dict —
    the exact keys/semantics the shipped MCP/CLI schema depends on. Accepted
    answers add a nested, versioned ``answer_surface`` derived deterministically
    from the parsed DAG, router decision, and final envelope; abstain/failure
    results carry ``answer_surface=None``. A projection inconsistency degrades to
    a labeled abstain. The diagnostics the benchmark needs live on
    :class:`NavDetail` behind that seam and never leak into this dict.

    See :func:`_run_navigate_detailed` for parameter semantics (especially
    ``max_nodes=-1`` default / ``0`` no caller count cap but token-bounded
    assembly / ``>0`` explicit hard cap), requested ``model`` forwarding, the
    injectable ``llm``, and labeled-abstain degradation on any
    assembly/model/navigation failure.
    """
    return _run_navigate_detailed(
        question,
        projects=projects,
        max_nodes=max_nodes,
        hops=hops,
        trace_floor=trace_floor,
        token_budget=token_budget,
        render_max_body_chars=render_max_body_chars,
        model=model,
        llm=llm,
    ).public