liveview

package
v2.93.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package liveview serves a knowledge graph as a live, rotatable 3D page.

A rendered file is true for the instant it was taken. This serves a page instead — a WebGL scene on 127.0.0.1 that keeps up with the graph on its own, so a graph being written can be watched rather than re-rendered.

Two things move on the page, and they arrive by different routes:

  • Structure — nodes and relations appearing or disappearing. Found by polling the graph and diffing, because it can be written by any process sharing it and there is no change feed to subscribe to. Only the difference is sent, so the layout keeps every node that did not change exactly where it had settled.
  • Activity — a query running, something being saved, a relation drawn. A query changes nothing in the graph, so no amount of polling can show one. These are reported by whoever handled the call, through Server.Observe, and light up the nodes they named.

A third thing on the page does not move, and says so by arriving differently. The contract panel answers how much of this store stands on what, and what on it is waiting for a person — the knowledge contract, read back through Source.Contract as a ContractReport. It is two aggregate scans and a filtered read over a shelf that changes when somebody reviews a record, so the panel fetches it from /api/contract on its own slow timer (ContractInterval) rather than riding the structure poll. A source that keeps no contract leaves the hook nil, and the panel says that in words: a store nobody can ask and a store nobody has graded are different findings, and the second is the one a real machine is usually in.

A caller supplies a Source, which is anything that can read nodes and edges. OpenSource builds one from the ambient CortexDB configuration (CORTEXDB_REMOTE for a shared brain, CORTEXDB_PATH otherwise), and LoadLocal and LoadRemote are exported for callers that know which graph they want and would rather say so than set environment variables.

The listener binds 127.0.0.1 and there is deliberately no option to widen it. The page is the whole graph with no authentication in front of it, so a listener on any other interface would be an unauthenticated read of everything it contains. An embedder that needs to expose it further should put its own authenticated proxy in front rather than ask for a wider bind.

Typical use:

src, err := liveview.OpenSource(ctx)
if err != nil {
	return err
}
sv, err := liveview.Start(ctx, src, liveview.DefaultPort, liveview.DefaultInterval, false)
if err != nil {
	return err
}
defer sv.Close()
fmt.Println(sv.URL())

Index

Constants

View Source
const (
	// ContractGraded is one of the contract's five closed values.
	ContractGraded = "graded"
	// ContractUntagged is every record carrying no _grade at all: a producer
	// that wrote nothing. On a shelf older than the contract this is the
	// largest number in the result.
	ContractUntagged = "untagged"
	// ContractUnknown is a _grade the contract does not define: a producer
	// writing something wrong. It is somebody's bug, and folding it in with
	// untagged would hide the one row a maintainer has to act on.
	ContractUnknown = "unknown"
)

Row kinds. Three, because untagged and unknown are not the same finding and the page must not have to work that out from a grade string it would then be keeping the contract's vocabulary in.

View Source
const (
	KindQuery  = "query"
	KindWrite  = "write"
	KindRelate = "relate"
)
View Source
const ContractInterval = 15 * time.Second

ContractInterval is how often the panel re-reads the contract.

Two orders of magnitude slower than the structure poll, because it is two aggregate scans rather than a keyed read, and because the thing it measures moves on the timescale of somebody reviewing a record. A tally fifteen seconds out of date has never been the wrong answer to "how much of this stands on what".

View Source
const DefaultInterval = 2 * time.Second

DefaultInterval is how often the brain is re-read for structural change. Activity does not wait for it — that arrives the moment a tool call is handled — so this only bounds how late a write from *another* machine shows up, and polling a database faster than a person can read the result is spend with nothing bought.

View Source
const DefaultPort = 37423

DefaultPort is the preferred port, chosen high and unusual so it does not collide with whatever else is being developed on this machine. When it is taken the server asks the OS for a free one instead of failing: a view is not worth an error, and the caller is told the URL either way.

Variables

This section is empty.

Functions

func ClipLabel

func ClipLabel(s string) string

ClipLabel shortens a node label for display, matching the local renderer.

Whitespace is collapsed before clipping. A node's label is often the first line of whatever text it came from, and that text has newlines in it — which survive into every view that prints the label as a string, breaking the layout of whatever panel is showing it.

func LoadLocal

func LoadLocal(ctx context.Context, sqlDB *sql.DB) ([]Node, []Edge, error)

LoadLocal reads meaningful nodes/edges from the live GraphRAG graph (everything except chunk nodes, and the edges between them). For large graphs it keeps the most-connected core: nodes are ranked by degree and capped, so the view shows the densely-linked hub instead of an arbitrary truncation with dangling edges.

func LoadRemote

func LoadRemote(ctx context.Context, addr, token string, limit int, quiet bool) ([]Node, []Edge, error)

LoadRemote pulls the whole entity graph from the shared brain.

quiet suppresses the truncation note. A one-shot render should say when it only drew part of the brain; the live view calls this every couple of seconds and would repeat the same line forever, which turns a useful notice into the only thing in the log.

func OpenInBrowser

func OpenInBrowser(url string) error

OpenInBrowser hands the URL to the desktop.

Errors are returned rather than ignored: a caller that claims to have opened something is worse than one that says it could not and gives you the URL.

func PortFromEnv

func PortFromEnv() int

func RemoteConfigured

func RemoteConfigured() (addr, token string, ok bool)

RemoteConfigured reports whether this process reads a shared brain.

Types

type ContractReport added in v2.93.0

type ContractReport struct {
	Available bool   `json:"available"`
	Reason    string `json:"reason,omitempty"`

	// Rows is the tally in display order: the contract's five, best-
	// established first, then untagged, then any unknown value. Ordering here
	// rather than in the page keeps the contract's vocabulary in Go, where it
	// is already written down once.
	//
	// When nothing at all carries a grade the five are omitted entirely and
	// only the untagged row survives. That is the honest shape for the state
	// a real machine is most likely in today: five empty bars describe a
	// measurement that was taken, and none was.
	Rows []ContractRow `json:"rows"`

	// Graded and Untagged are records, not rows: graded counts everything
	// carrying any _grade, an unrecognised one included.
	Graded   int `json:"graded"`
	Untagged int `json:"untagged"`
	// Nodes and Edges are the store's own totals, so the panel can say that it
	// counted the whole shelf and not the six hundred nodes on screen.
	Nodes int `json:"nodes"`
	Edges int `json:"edges"`

	// Attention is held and refused together, each with the reason its
	// producer gave. Told apart by Grade, not split into two lists — a reader
	// working through a shelf wants both, and two lists makes it likely only
	// one gets rendered.
	Attention []cortexdb.GradedRecord `json:"attention"`
	Truncated bool                    `json:"truncated,omitempty"`
	// Total is everything held or refused, whether or not it is in Attention.
	Total int `json:"total"`

	// At is when this was read, so the panel can show its own staleness rather
	// than leave a slow number looking like a stuck one.
	At int64 `json:"at"`
}

ContractReport is what the panel draws: how much of the store stands on what, and what on it is waiting for a person.

Available and Reason exist because "this view cannot read the contract" is a different answer from "nothing here is graded", and a panel that rendered both as an empty chart would be lying about the more common one. A side graph, or a brain too old to answer, sets Available false and says so.

type ContractRow added in v2.93.0

type ContractRow struct {
	// Grade is the contract value, verbatim — empty for the untagged row, and
	// for an unknown row whatever the producer actually wrote, so the
	// maintainer can go and find it.
	Grade string `json:"grade"`
	Kind  string `json:"kind"`
	Nodes int    `json:"nodes"`
	Edges int    `json:"edges"`
}

ContractRow is one line of the tally.

Nodes and Edges stay apart rather than summed, for the reason graph.PropertyCount gives: an edge is an assertion about two things and a node is one thing, and a reader told "40 records" cannot tell a graph of 4 nodes and 36 edges from its reverse.

func (ContractRow) Total added in v2.93.0

func (r ContractRow) Total() int

Total is the row's records, for scaling a bar against its neighbours.

type Delta

type Delta struct {
	Version      int64    `json:"version"`
	AddedNodes   []Node   `json:"added_nodes"`
	RemovedNodes []string `json:"removed_nodes"`
	AddedEdges   []Edge   `json:"added_edges"`
	RemovedEdges []Edge   `json:"removed_edges"`
	// Nodes and Edges are the totals after applying, so the page's counters
	// cannot drift out of step with the brain if a delta is ever missed.
	Nodes int `json:"nodes"`
	Edges int `json:"edges"`
}

Delta is what changed between two snapshots.

AddedNodes carries upsert semantics rather than insert: a node whose label or type changed is re-sent here, not removed and re-added, so the view updates it in place and keeps the position the layout already settled on.

func Diff

func Diff(prev, next Snapshot) Delta

Diff computes what changed from prev to next.

func (Delta) Empty

func (d Delta) Empty() bool

Empty reports whether the delta changes nothing, so the poller can stay quiet instead of waking every connected page on a timer.

type Edge

type Edge struct {
	Source string `json:"source"`
	Target string `json:"target"`
	Label  string `json:"label"`
}

type Event

type Event struct {
	Seq  int64  `json:"seq"`
	At   int64  `json:"at"` // unix milliseconds
	Kind string `json:"kind"`
	Tool string `json:"tool"`
	// Text is the line the ticker shows.
	Text string `json:"text"`
	// Terms are the strings the page lights nodes up by. They are matched
	// loosely against labels and ids rather than joined on node identity: a
	// tool's arguments name things the way a person does ("CortexDB"), and the
	// graph keys them the way a database does ("entity:CortexDB").
	Terms []string `json:"terms,omitempty"`
	// Links are from/to pairs to draw a pulse along, for relation writes.
	Links  [][2]string `json:"links,omitempty"`
	Failed bool        `json:"failed,omitempty"`
}

Event is one thing the brain just did.

func ClassifyToolCall

func ClassifyToolCall(tool string, args json.RawMessage, failed bool) (Event, bool)

ClassifyToolCall turns one MCP tool call into an event for the live view, reporting false for calls the view should ignore.

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub holds the current graph and fans structure and activity out to every page watching.

Slow readers are dropped rather than waited on: a browser tab that stopped draining its stream must never be able to stall the MCP server that is feeding it, because that server is also the one answering the agent.

func NewHub

func NewHub() *Hub

type Message

type Message struct {
	Delta *Delta `json:"delta,omitempty"`
	Event *Event `json:"event,omitempty"`
}

Message is one server-sent event: exactly one of the two is set.

type Node

type Node struct {
	ID    string `json:"id"`
	Label string `json:"label"`
	Type  string `json:"type"`
}

type Payload

type Payload struct {
	Version  int64   `json:"version"`
	Nodes    []Node  `json:"nodes"`
	Edges    []Edge  `json:"edges"`
	Events   []Event `json:"events"`
	Source   string  `json:"source"`
	Activity bool    `json:"activity"`
	Interval int64   `json:"interval_ms"`
}

Payload is the page's opening state: everything needed to draw, plus what the header reports about where it came from.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server serves one live view.

func Current

func Current() *Server

Current returns the running view of the brain, or nil. Used by the middleware, which must not start a server just because a tool was called — the view exists only once someone asks to see it.

func CurrentFor

func CurrentFor(key string) *Server

CurrentFor returns the running view of one graph, or nil.

func Shared

func Shared(ctx context.Context, activity bool) (*Server, error)

Shared returns the process's view of the brain, starting it on first use.

func SharedFor

func SharedFor(ctx context.Context, key string, activity bool, open func(context.Context) (*Source, error)) (*Server, error)

SharedFor returns the process's view of one graph, keyed by name, starting it on first use. The empty key is the brain.

Keyed rather than single because a process can now be asked for more than one graph, and a second request for the same one means "show me", not "make me another": a view is a port and a poller, and handing out a new pair every time an agent asked twice would leave a trail of them behind.

Each view binds its own port. Only the first gets the preferred one; the rest take whatever the OS gives, which is why the URL is always returned rather than assumed.

func Start

func Start(ctx context.Context, src *Source, port int, interval time.Duration, activity bool) (*Server, error)

Start reads the graph once, starts serving, and starts polling. It returns as soon as the URL is usable.

func (*Server) Close

func (s *Server) Close() error

Close stops serving and releases the brain.

func (*Server) Observe

func (s *Server) Observe(ev Event)

observe is the hook the MCP middleware calls for every handled tool call.

func (*Server) Snapshot

func (s *Server) Snapshot() Snapshot

Snapshot is the graph as last read.

func (*Server) SourceName

func (s *Server) SourceName() string

SourceName names the brain this view reads, for a caller that wants to say so.

func (*Server) URL

func (s *Server) URL() string

URL is where the view is.

func (*Server) WatchesCalls

func (s *Server) WatchesCalls() bool

WatchesCalls reports whether tool calls reach this view. A view polling a database sees structure only; one fed by an MCP server sees the queries too, and the page says which rather than leaving a still ticker to be read as a fault.

type Snapshot

type Snapshot struct {
	Version int64  `json:"version"`
	Nodes   []Node `json:"nodes"`
	Edges   []Edge `json:"edges"`
}

Snapshot is one reading of the brain's entity graph.

type Source

type Source struct {
	Describe string
	Read     func(ctx context.Context) ([]Node, []Edge, error)
	// Contract answers the knowledge contract's two questions about this
	// store: how much of it stands on what, and what on it needs a person.
	//
	// Optional, and nil is a legitimate answer rather than an oversight: a
	// source can be a graph that keeps no contract metadata at all — a side
	// graph assembled in memory, say. The panel says so in words instead of
	// drawing an empty chart over it, so nil never reads as "nothing here is
	// graded", which is a different and much more common finding.
	Contract func(ctx context.Context) (ContractReport, error)
	Close    func() error
}

Source is where a live server reads the graph from.

func OpenSource

func OpenSource(ctx context.Context) (*Source, error)

OpenSource opens whichever brain this process is configured for. The local database is opened once and held: the poller reads it every couple of seconds, and reopening the file on that cadence would be pointless churn.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL