Documentation
¶
Overview ¶
Package leyline provides Go bindings to the ley-line C FFI.
The actual implementation requires the "leyline" build tag and a locally built libleyline_fs.a. See client.go for details.
Package leyline — semantic.go provides typed methods for the ley-line daemon's semantic search operations (embedding-based KNN search).
SemanticClient wraps a SocketClient and translates between mache's search requests and the daemon's semantic_search / embed_status / embed_content ops. All methods are no-ops when the underlying SocketClient is nil, making the integration fully optional.
Package leyline — sheaf.go provides typed methods for the ley-line daemon's sheaf (topology-aware cache invalidation) operations.
SheafClient wraps a SocketClient and translates between mache's community detection output and the daemon's sheaf_* UDS ops. All methods are no-ops when the underlying SocketClient is nil, making the integration fully optional.
Package leyline — trigger.go pushes graph content to the ley-line daemon for embedding. Called as a fire-and-forget goroutine after graph construction.
Index ¶
- Constants
- func ComputeStalk(memberCount int, crossTokens []string) []float32
- func DaemonSource() string
- func DiscoverOrStart() (string, error)
- func DiscoverSocket() (string, error)
- func ResolveBinary(allowDownload bool) (string, error)
- func SetDaemonSource(dir string)
- func StopManaged()
- func TriggerEmbedding(g graph.Graph, batchSize int)
- func VerifyReachableDaemonVersion() error
- type EmbedStatus
- type EventHandler
- type FindCalleesResponse
- type FindCallersResponse
- type GetNodeResponse
- type LeylineProvenance
- type ListChildrenResponse
- type Node
- type NodeContent
- type ReadContentResponse
- type Ref
- type SemanticClient
- type SemanticResult
- type SheafClient
- func (sc *SheafClient) Defect() (float64, error)
- func (sc *SheafClient) Invalidate(regionID int) ([]int, error)
- func (sc *SheafClient) InvalidateWithStalk(regionID int, newHash string, newData []float32) ([]int, error)
- func (sc *SheafClient) PushTopology(cr *graph.CommunityResult, refs map[string][]string) error
- func (sc *SheafClient) Status() (SheafStatus, error)
- type SheafInvalidateEvent
- type SheafStatus
- type SheafSubscriber
- type SocketClient
- func (c *SocketClient) Close() error
- func (c *SocketClient) Enrich(pass string, files []string) (map[string]any, error)
- func (c *SocketClient) Prioritize(files []string) error
- func (c *SocketClient) Query(sql string) ([][]any, error)
- func (c *SocketClient) SendOp(req map[string]any) (map[string]any, error)
- func (c *SocketClient) SendOpInto(req map[string]any, dest any) error
- func (c *SocketClient) SetDeadline(t time.Time) error
- func (c *SocketClient) Subscribe(topics []string) (<-chan map[string]any, error)
- func (c *SocketClient) SubscribeDropped() uint64
- func (c *SocketClient) VerifyVersion(clientVersion string) error
- type SubscriberState
- type SubscriberStatus
Constants ¶
const ( // ScopeChangedOnly is the fine-grained per-region contract: // invalidate only the caches for regions listed in RegionIDs. // Emitted by: // - the consumer-driven `sheaf.invalidate` topic (payload has // no `scope` field; mache infers this variant), // - the future fine-grained daemon-driven emit (LLO bead // `e40566`, not yet shipped). ScopeChangedOnly = "changed-only" // ScopeAllKnown is the coarse-v1 contract shipped by LLO v0.6: // evict every sheaf-scoped cache entry. Region IDs are still // carried (they list every known region) but callers should NOT // treat them as a whitelist — the daemon is reporting the whole // working set as stale. ScopeAllKnown = "all-known" )
Sheaf-invalidation scope values carried on the wire.
The scope field is emitted by LLO v0.6+ on the watcher-driven `daemon.sheaf.invalidate` topic (LLO PR #140, bead `ley-line-open-3b3476`). The pre-v0.6 consumer-driven `sheaf.invalidate` topic does not carry the field — mache treats its absence as ScopeChangedOnly (the historical per-region behavior) so existing callers keep working unchanged.
Variables ¶
This section is empty.
Functions ¶
func ComputeStalk ¶ added in v0.9.0
ComputeStalk derives a single region's 32-D δ⁰ stalk from its community membership + the cross-community tokens it touches. Exported so the watcher path (mache-c11848) can compute a fresh stalk to pass to InvalidateWithStalk.
The agreement-dim portion is purely a function of `crossTokens`, so a content change that does not alter which cross-community tokens the region exposes will produce the same agreement coords and the daemon's δ⁰ check will (correctly) skip the cascade.
func DaemonSource ¶ added in v0.10.0
func DaemonSource() string
DaemonSource returns the configured daemon --source dir, or "" if unset.
func DiscoverOrStart ¶
DiscoverOrStart finds a running ley-line daemon socket, or auto-starts a managed daemon subprocess if the leyline binary is on PATH.
The managed daemon uses ~/.mache/ as its data directory:
~/.mache/default.arena — arena file ~/.mache/default.ctrl — control block ~/.mache/default.sock — UDS socket (what we connect to) ~/.mache/mount/ — FUSE/NFS mount point
The subprocess is killed when the mache process exits (via atexit cleanup registered on first spawn). Only one managed daemon per process.
Dedup (mache-52a23a): every "is there already a socket?" check verifies the socket is *live* (a Unix dial connects) before returning the path. A stale socket file left behind by a SIGKILL'd daemon would otherwise be returned by findExistingSocket and cause the caller to fail at first DialSocket. Worse, *between* the stat and the spawn, the entire managed daemon path can also be re-entered, orphaning daemons under concurrent callers within the same process. The full DiscoverOrStart now runs under managed.mu so the discover-then-spawn sequence is atomic across goroutines; stale on-disk sockets are removed before spawning so the daemon can bind the well-known path cleanly.
func DiscoverSocket ¶
DiscoverSocket finds the ley-line socket path by checking:
- LEYLINE_SOCKET environment variable
- ~/.mache/default.sock (well-known kiln deployment path)
Returns the path and nil error if a socket file exists, or an error if no socket can be found. Does NOT auto-start a daemon.
func ResolveBinary ¶ added in v0.13.0
downloadLeyline fetches the pinned-version leyline binary from GitHub releases (see leylineBinaryVersion) to the specified path. Returns the path on success. ResolveBinary locates a usable leyline binary and returns its path. Resolution order: PATH → ~/.mache/bin/leyline → (when allowDownload) auto-download the pinned ley-line-open release. This is the single provisioning chokepoint shared by the serve path (autoInvokeLeylineParse) and the build path, so `mache build` and `mache serve` acquire leyline identically instead of the build path silently degrading to tree-sitter.
Auto-download is skipped — returning an error rather than fetching — when allowDownload is false, or when MACHE_NO_LEYLINE is set (the CI/offline opt-out). A downloaded binary is cached at ~/.mache/bin/leyline for reuse.
func SetDaemonSource ¶ added in v0.10.0
func SetDaemonSource(dir string)
SetDaemonSource configures the source directory passed to the auto-spawned leyline daemon. Call once at serve startup when serving a source tree. Safe for concurrent use; takes effect on the next daemon spawn (a daemon already running is not restarted).
func StopManaged ¶
func StopManaged()
StopManaged gracefully stops the auto-spawned leyline daemon, if any. Sends SIGTERM first so leyline can unmount NFS cleanly, then waits up to 3 seconds before falling back to SIGKILL. Without this, macOS shows a "Server connections interrupted" dialog for the stale NFS mount. Safe to call multiple times. Called automatically by cleanup hooks.
func TriggerEmbedding ¶ added in v0.6.1
TriggerEmbedding walks all file nodes in the graph and pushes their content to the ley-line daemon for embedding via the embed_content op. Batches nodes in groups of batchSize. Logs errors but does not fail.
func VerifyReachableDaemonVersion ¶ added in v0.11.0
func VerifyReachableDaemonVersion() error
VerifyReachableDaemonVersion is the startup wire-compat handshake: if a leyline daemon is ALREADY reachable (it never auto-starts one), it queries the leyline_version op and refuses to proceed on a structural mismatch. It returns nil when no daemon is reachable, when the daemon predates the leyline_version op, or when the versions are compatible — a version probe must never be stricter than the actual wire decode.
Types ¶
type EmbedStatus ¶ added in v0.6.1
type EmbedStatus struct {
Ready bool `json:"ready"`
Count int `json:"count"`
Dimensions int `json:"dimensions"`
Model string `json:"model"`
}
EmbedStatus reports the state of the embedding index.
type EventHandler ¶ added in v0.9.0
type EventHandler func(SheafInvalidateEvent)
EventHandler is called once per pushed sheaf.invalidate event. Handler may be invoked concurrently across reconnect boundaries — implementations must guard their own state.
type FindCalleesResponse ¶ added in v0.9.0
type FindCalleesResponse struct {
OK *bool `json:"ok"`
Callees []Ref `json:"callees"`
Error *string `json:"error,omitempty"`
}
FindCalleesResponse is the response shape for op=find_callees (added in LLO 0.2.2). Daemon executes the JOIN against node_refs + node_defs and returns the resolved construct rows.
type FindCallersResponse ¶ added in v0.9.0
type FindCallersResponse struct {
OK *bool `json:"ok"`
Callers []Ref `json:"callers"`
Error *string `json:"error,omitempty"`
}
FindCallersResponse is the response shape for op=find_callers.
type GetNodeResponse ¶ added in v0.9.0
type GetNodeResponse struct {
OK *bool `json:"ok"`
Node *Node `json:"node,omitempty"`
Error *string `json:"error,omitempty"`
}
GetNodeResponse is the response shape for op=get_node.
type LeylineProvenance ¶ added in v0.10.0
type LeylineProvenance struct {
Path string `json:"path"`
Version string `json:"version"` // as reported by `<bin> --version` (best-effort)
Source string `json:"source"` // PATH | cached | downloaded
ExpectedVersion string `json:"expected_version"` // the compile-time pin (leylineBinaryVersion)
VersionMatches bool `json:"version_matches"` // reported version contains the pinned tag
}
LeylineProvenance is the observable provenance of the leyline binary mache resolved for the managed daemon. Surfaced so "which leyline is mache using?" is a log grep / MCP tool call instead of an archaeology dig (the failure mode that hid the stale-cached-0.4.1 skew, mache-0acdf6).
func Provenance ¶ added in v0.10.0
func Provenance() (LeylineProvenance, bool)
Provenance returns the resolved leyline binary's provenance, or ok=false if mache hasn't resolved one this process. The returned ExpectedVersion is always populated (the compile-time pin) even when ok is false.
type ListChildrenResponse ¶ added in v0.9.0
type ListChildrenResponse struct {
OK *bool `json:"ok"`
Children []Node `json:"children"`
Error *string `json:"error,omitempty"`
}
ListChildrenResponse is the response shape for op=list_children. Failure path carries Error; mache treats "not found" specially.
type Node ¶ added in v0.9.0
type Node struct {
ID *string `json:"id"`
ParentID *string `json:"parent_id"`
Name *string `json:"name"`
Kind *int32 `json:"kind"`
Size *int64 `json:"size,string"`
Record *string `json:"record,omitempty"`
}
Node represents an entry in the projected graph. Used by both list_children (Record omitted to keep directory listings small) and get_node (Record present when the SQL record column is non-null).
type NodeContent ¶ added in v0.6.1
NodeContent is a node to embed: id + text content.
type ReadContentResponse ¶ added in v0.9.0
type ReadContentResponse struct {
OK *bool `json:"ok"`
Content *string `json:"content,omitempty"`
Error *string `json:"error,omitempty"`
}
ReadContentResponse is the response shape for op=read_content.
type Ref ¶ added in v0.9.0
Ref is a {node_id, source_id} pair returned by find_callers / find_callees / find_defs. SourceID is the construct's source-file node.
type SemanticClient ¶ added in v0.6.1
type SemanticClient struct {
// contains filtered or unexported fields
}
SemanticClient provides typed access to ley-line's embedding operations. A nil SemanticClient is safe — all methods return zero values without error.
func NewSemanticClient ¶ added in v0.6.1
func NewSemanticClient(sock *SocketClient) *SemanticClient
NewSemanticClient wraps an existing SocketClient. sock may be nil.
func (*SemanticClient) EmbedContent ¶ added in v0.6.1
func (sc *SemanticClient) EmbedContent(nodes []NodeContent) (int, error)
EmbedContent pushes node content to the daemon for embedding. Returns the number of nodes successfully embedded.
func (*SemanticClient) Search ¶ added in v0.6.1
func (sc *SemanticClient) Search(query string, k int) ([]SemanticResult, error)
Search performs a semantic search over embedded nodes. Returns results sorted by distance (closest first).
func (*SemanticClient) Status ¶ added in v0.6.1
func (sc *SemanticClient) Status() (EmbedStatus, error)
Status returns the current embedding index status.
type SemanticResult ¶ added in v0.6.1
SemanticResult is a single KNN search result.
type SheafClient ¶ added in v0.6.1
type SheafClient struct {
// contains filtered or unexported fields
}
SheafClient provides typed access to ley-line's sheaf operations. A nil SheafClient is safe — all methods return zero values without error.
func NewSheafClient ¶ added in v0.6.1
func NewSheafClient(sock *SocketClient) *SheafClient
NewSheafClient wraps an existing SocketClient. sock may be nil.
func (*SheafClient) Defect ¶ added in v0.6.1
func (sc *SheafClient) Defect() (float64, error)
Defect queries the global consistency defect score. Returns 0.0 when the daemon is unavailable.
func (*SheafClient) Invalidate ¶ added in v0.6.1
func (sc *SheafClient) Invalidate(regionID int) ([]int, error)
Invalidate marks a region as stale without pushing new stalk data. The daemon's cascade falls back to the heuristic XOR-of-endpoints pre-filter for this op. Callers that have the post-change stalk available should prefer InvalidateWithStalk so the daemon's δ⁰ check decides whether the change actually crossed the agreement plane.
func (*SheafClient) InvalidateWithStalk ¶ added in v0.9.0
func (sc *SheafClient) InvalidateWithStalk(regionID int, newHash string, newData []float32) ([]int, error)
InvalidateWithStalk reports regionID changed and pushes the new 32-D f32 stalk so the daemon can evaluate δ⁰ against the baseline recorded at sheaf_set_topology time. Returns the BFS-ordered list of regions the daemon determined are transitively affected.
newHash may be empty — the daemon will infer the hash from data when both `hash` and `data` are present. When both hash and data are empty, this is equivalent to Invalidate.
Returns an error when newData is non-empty but len(newData) is not stalkDim — a wrong-sized vector would either be rejected daemon-side with a confusing error or, worse, cause the δ⁰ projection to silently mis-align with restriction agreement_dim and skip the cascade entirely.
func (*SheafClient) PushTopology ¶ added in v0.6.1
func (sc *SheafClient) PushTopology(cr *graph.CommunityResult, refs map[string][]string) error
PushTopology converts Louvain community detection output into a sheaf_set_topology op and sends it to the daemon.
Each community maps to a region whose hash is the SHA-256 of the sorted, concatenated member node IDs. Each region also carries a 32-D f32 stalk: the first 30 dims are SHA-256 of the region's cross-community tokens (so two regions agree iff they share the same set of cross-boundary refs), and the trailing 2 dims are private discriminators (member count + cross-degree). Restriction edges carry `agreement_dim=30` so the daemon's δ⁰ check projects the agreement subspace.
With these inputs the daemon engages δ⁰-driven invalidation (response advertises `delta_zero_mode: true`). Without the f32 data the daemon silently falls back to heuristic-only mode, which is correct but loses the precision guarantee from LLO PR #16.
func (*SheafClient) Status ¶ added in v0.6.1
func (sc *SheafClient) Status() (SheafStatus, error)
Status returns the full sheaf status from the daemon.
type SheafInvalidateEvent ¶ added in v0.9.0
type SheafInvalidateEvent struct {
// Invalidated carries the region identifiers the daemon named in
// the payload. Populated from `invalidated` (consumer-driven) or
// `region_ids` (watcher-driven). Interpretation depends on Scope
// (see the constants above): under ScopeChangedOnly this is a
// whitelist to invalidate; under ScopeAllKnown it's a snapshot of
// every known region and callers should evict everything.
//
// The field keeps its pre-v0.6 name (rather than being renamed to
// `RegionIDs`) to preserve the public-API contract of the mache
// event surface. The `region_ids` wire field is decoded into it
// too.
Invalidated []int
// Generation is the daemon's monotonic counter after the emit.
// Agents compare this to a previously-cached value to decide
// whether their snapshot is still fresh.
Generation uint64
// PriorGeneration is the counter value before this event's bump.
// Populated on both topics.
PriorGeneration uint64
// Count mirrors len(Invalidated); kept as a separate wire field
// because the daemon emits it that way.
Count int
// Scope classifies the event: [ScopeChangedOnly] or [ScopeAllKnown].
// See the constants for semantics + wire-emission provenance. The
// dispatcher treats an unknown non-empty value as ScopeAllKnown
// (over-invalidate rather than serve stale) and logs a warning so
// a new future scope value isn't silently dropped.
//
// Empty when parsed from the consumer-driven topic (which
// pre-dates the field) — cmd routing infers ScopeChangedOnly for
// the empty case.
Scope string
// ChangedFiles lists the files whose reparse triggered the emit.
// Watcher-driven topic only; empty from the consumer-driven topic
// (which has no file context — the caller decides which regions
// to poke).
ChangedFiles []string
// CurrentRoot is the 64-hex BLAKE3 root of the substrate state
// after the emit. Watcher-driven topic only; may be empty when
// the daemon's controller read failed (best-effort, degrades
// honestly per LLO PR #140).
CurrentRoot string
// TimestampMs is now_ms() at emit time. Watcher-driven topic only.
TimestampMs int64
}
SheafInvalidateEvent is the parsed shape of a sheaf-invalidate event pushed by the daemon. Two on-wire variants collapse to this struct:
- Topic `sheaf.invalidate` (LLO ≤ v0.5, consumer-driven — emitted from `op_sheaf_invalidate` when a client calls it): `{invalidated, count, generation, prior_generation}`.
- Topic `daemon.sheaf.invalidate` (LLO v0.6+, watcher-driven — emitted from the daemon's enrichment cycle, LLO PR #140): `{region_ids, count, scope, changed_files, current_root, generation, prior_generation, timestamp_ms}`.
`Generation` and `PriorGeneration` are parsed from either raw JSON numbers or capnp-json quoted-string Int64 (see parseUint64) so a single struct handles both encodings without a per-topic parse.
type SheafStatus ¶ added in v0.6.1
type SheafStatus struct {
Generation uint64 `json:"generation"`
Valid int `json:"valid"`
Total int `json:"total"`
Defect float64 `json:"defect"`
}
SheafStatus mirrors the response from sheaf_status / sheaf_defect.
type SheafSubscriber ¶ added in v0.9.0
type SheafSubscriber struct {
// contains filtered or unexported fields
}
SheafSubscriber owns a long-lived `subscribe` connection to the ley-line daemon, dispatching pushed sheaf-invalidate events to a handler.
Two topics feed the same handler:
- `sheaf.invalidate` — pre-v0.6 consumer-driven emit from `op_sheaf_invalidate` (mache calls this from its own watcher via SheafClient.Invalidate).
- `daemon.sheaf.invalidate` — LLO v0.6+ watcher-driven emit from the daemon's own enrichment cycle (LLO PR #140 / bead `ley-line-open-3b3476`), carrying the coarse-v1 payload shape documented on SheafInvalidateEvent.
Both variants decode to a single SheafInvalidateEvent; the routing layer in cmd/sheaf_subscribe.go dispatches on the Scope field.
FIXED UPSTREAM (LLO v0.4.3, ley-line-open-5caa59): pre-v0.4.3 LLO daemons did NOT actually deliver sheaf.invalidate events to UDS subscribers — `handle_connection` was a pure request/response loop that never drained `ConnectionState.event_rx`, so emitted events silently accumulated. The cascade math + op response were always correct; only the event-bus push was broken. Other topics (daemon.snapshot) appeared to work because they happened to be included in the subscribe-response replay batch. LLO v0.4.3 ships a per-connection writer task plus a per-subscribe event-relay task; mache requires v0.4.3 or later for daemon-pushed cascades.
tools/sheaf-subscribe-probe/main.go is the canonical repro tool; internal/leyline/sheaf_subscriber_e2e_test.go is the regression guard that catches a future re-emergence of this bug.
Design constraints (locked in by PR for mache-c14c43):
Dedicated SocketClient (not shared with the watcher's SheafClient). The Subscribe primitive owns the read side of the conn after activation, and would race any concurrent SendOp — see PR #383's SocketClient docstring. Mache should hold one SocketClient for ops and one for events.
One subscriber per process. Spawned at serve startup, routes events through a single handler that the caller (cmd/serve) uses to fan out to all per-graph invalidators.
Reconnect with exponential backoff. The daemon may restart; the subscriber must redial transparently without leaking goroutines or spinning when the socket is permanently absent. Backoff starts at 1s and caps at 30s in production; tests override via SetBackoffForTest.
Observable state. Status() returns the current connection state + last-event time + last-seen generation. cmd/serve_handlers' new get_sheaf_status MCP tool will surface this so agents have an honest "cascade engaged?" signal.
Unmapped events log + skip, not buffer. The handler is responsible for mapping region IDs → node IDs via the current CommunityResult; when no result is installed yet, drop the event (the cascade for that window is unreachable anyway, and any cached results are equally pre-result-installation stale).
func NewSheafSubscriber ¶ added in v0.9.0
func NewSheafSubscriber(sockPath string, handler EventHandler) *SheafSubscriber
NewSheafSubscriber creates a subscriber that will dial sockPath and dispatch events to handler. Use Start to begin the loop; Stop to shut it down cleanly.
Both lifecycle channels are constructed here so Stop is safe to call in any order relative to Start (see Stop's contract).
func (*SheafSubscriber) SetBackoffForTest ¶ added in v0.9.0
func (s *SheafSubscriber) SetBackoffForTest(min, max time.Duration)
SetBackoffForTest overrides the dial-retry backoff bounds. Test-only; production callers must not touch this. Kept on the public surface (rather than exposed via build tag) because the alternative — making the bounds a constructor option — would force every caller to thread production defaults through.
func (*SheafSubscriber) Start ¶ added in v0.9.0
func (s *SheafSubscriber) Start(ctx context.Context)
Start spawns the subscribe loop in a background goroutine. Returns immediately. The loop runs until Stop is called or ctx is cancelled.
Idempotent — repeated calls after the first are no-ops (sync.Once gate). Concurrent Start calls collapse to a single loop spawn so two goroutines can't accidentally race for the conn.
func (*SheafSubscriber) Status ¶ added in v0.9.0
func (s *SheafSubscriber) Status() SubscriberStatus
Status returns a snapshot of the subscriber's current state. Cheap; callers (e.g. the get_sheaf_status MCP handler) can poll freely.
func (*SheafSubscriber) Stop ¶ added in v0.9.0
func (s *SheafSubscriber) Stop()
Stop signals the subscribe loop to terminate and blocks until it has.
Contract:
- When Start was never called, returns immediately (no goroutine to stop, no channel to wait on).
- When Start was called, every Stop call (including concurrent and repeated ones) waits until run() has actually returned. Returning before that would let callers observe a Disconnected state too early — the contract is "by the time Stop returns, the loop is gone." Multiple Stop calls all observe the same close on doneCh (a closed channel is permanently ready) so the wait is cheap after the first one completes.
Fixed in response to PR #384 Copilot #1: the prior shape blocked forever pre-Start and short-circuited on a "stopped" flag for repeated calls, violating both guarantees.
type SocketClient ¶
type SocketClient struct {
// contains filtered or unexported fields
}
SocketClient communicates with a running ley-line daemon over its Unix domain control socket ({ctrl}.sock).
SendOp / SendOpInto are safe for concurrent use: sendMu serializes the write-then-read pair so the line-delimited JSON protocol can't get crossed when multiple callers (e.g. the file watcher firing multiple debounce timers after a save burst) hit the same client simultaneously. The protocol is request/response — interleaving produces a response for the wrong caller, which the daemon can't detect since there's no per-message correlation ID.
Subscribe is the exception: once a Subscribe goroutine takes ownership of the read side, ad-hoc SendOp on the same connection is unsupported and will race the subscription's event reads. The canonical usage is one SocketClient per role (one for ops, one for subscribe).
func DialSocket ¶
func DialSocket(sockPath string) (*SocketClient, error)
DialSocket connects to the ley-line control socket at sockPath. The socket path is typically derived from the control path: e.g. /tmp/leyline.ctrl → /tmp/leyline.sock
func (*SocketClient) Close ¶
func (c *SocketClient) Close() error
Close closes the underlying connection. Safe to call multiple times from any number of goroutines — the sync.Once gates the underlying conn.Close so concurrent shutdown can't race the Subscribe read goroutine (see closeOnce docstring on SocketClient for the history). The conn pointer is intentionally NOT zeroed; closed conn errors from subsequent reads/writes propagate normally to their callers.
func (*SocketClient) Enrich ¶ added in v0.7.0
Enrich runs a daemon enrichment pass (e.g. "lsp", "embed") over the given files via the `enrich` op, populating the corresponding arena tables (for "lsp": _lsp_hover / _lsp / _lsp_defs / _lsp_refs). `files` are paths relative to the daemon's --source dir; nil/empty enriches every file. The op is SYNCHRONOUS on the daemon side — it runs the pass and snapshots the living db to the arena before responding — so a subsequent Query observes the populated tables.
This replaces the prior `tool` op: the leyline daemon's UDS dispatch speaks named ops directly ({"op":"enrich","pass":...}); the MCP tools/call envelope ({"op":"tool","name":...}) only exists on the HTTP /mcp endpoint, so sending it over UDS returned "unknown op: tool" (mache-303036).
func (*SocketClient) Prioritize ¶ added in v0.7.0
func (c *SocketClient) Prioritize(files []string) error
Prioritize requests the daemon to parse the given files on the next pass. Non-blocking from the daemon's perspective: it acknowledges the request and schedules the files for priority parsing asynchronously.
func (*SocketClient) Query ¶
func (c *SocketClient) Query(sql string) ([][]any, error)
Query runs a SQL query against the active arena buffer via the `query` op and returns the rows as [][]any (one slice per row, column order matching the SELECT / the response's `columns` field).
The daemon's `query` op returns each row as a JSON OBJECT keyed by column name ({"node_id":..,"hover_text":..}), with a separate ordered `columns` array — NOT an array of values. (A legacy array-of-arrays shape is also accepted for forward/back-compat.) The prior parser only handled the array shape and silently dropped every object row, so any query against the daemon returned zero rows — which is exactly why LSP get_type_info surfaced no hover even though enrichment populated _lsp_hover (mache-303036).
func (*SocketClient) SendOp ¶
SendOp sends a JSON request and reads the JSON response. Both are line-delimited (newline-terminated JSON).
func (*SocketClient) SendOpInto ¶ added in v0.9.0
func (c *SocketClient) SendOpInto(req map[string]any, dest any) error
SendOpInto sends a JSON request and decodes the response into dest. Use this for ops with typed response structs (see wire.go); SendOp's map[string]any return silently zeros Int64 fields under the post-b0ea2e daemon wire (capnp-json codec emits int64 as JSON strings).
func (*SocketClient) SetDeadline ¶
func (c *SocketClient) SetDeadline(t time.Time) error
SetDeadline sets the read/write deadline on the underlying connection.
func (*SocketClient) Subscribe ¶ added in v0.7.0
func (c *SocketClient) Subscribe(topics []string) (<-chan map[string]any, error)
Subscribe sends a subscribe op and returns a channel that receives pushed events from the daemon. The subscription runs until the connection closes.
Topics use dot-separated hierarchical matching:
- "daemon.snapshot" — exact match
- "daemon.*" — one segment wildcard
- "daemon.**" — any segments wildcard
The returned channel is buffered (64). If the consumer falls behind, events are dropped (the daemon handles overflow per its policy).
func (*SocketClient) SubscribeDropped ¶ added in v0.9.0
func (c *SocketClient) SubscribeDropped() uint64
SubscribeDropped returns the cumulative number of events the Subscribe goroutine has dropped because the consumer's channel buffer was full. Non-zero values mean the consumer is falling behind the daemon's event rate; investigate before treating cache state as authoritative.
func (*SocketClient) VerifyVersion ¶ added in v0.11.0
func (c *SocketClient) VerifyVersion(clientVersion string) error
VerifyVersion queries the daemon's leyline_version op and checks wire-format and schema compatibility against clientVersion (this build's leyline schema-client version). A daemon that predates the op (SendOp errors) is not an error — older daemons simply can't be verified, and we must not refuse a daemon we might still decode fine.
type SubscriberState ¶ added in v0.9.0
type SubscriberState int
SubscriberState classifies the subscriber's connection state. Surfaced via Status() and ultimately through get_sheaf_status.
const ( // StateDisconnected: not currently connected; either not yet started // or the loop exited (Stop called, ctx cancelled, fatal error). StateDisconnected SubscriberState = iota // StateConnecting: between dial attempts (in backoff) or actively dialing. StateConnecting // StateConnected: subscribed and listening for events. StateConnected )
func (SubscriberState) String ¶ added in v0.9.0
func (s SubscriberState) String() string
String renders the state for logging / MCP responses. Lower-case since it appears in agent-consumable JSON.
type SubscriberStatus ¶ added in v0.9.0
type SubscriberStatus struct {
State SubscriberState
Reason string // non-empty when State != Connected, explains why
LastEvent time.Time // zero when no event seen yet
LastGeneration uint64 // 0 when no event seen yet
}
SubscriberStatus is the snapshot returned by SheafSubscriber.Status().