liveview

package
v2.100.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 21 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.

There is a second page, at /ontology, and it draws a different graph about the same store: not what is in this brain but what it is allowed to talk about — tens of declared object types with link types between them, read back through Source.Ontology as an OntologyReport. It is drawn as a deterministic 2D diagram rather than in the scene next door, for the reason spelled out over ontologyHTML: an ontology is tens of named nodes with declared structure — interfaces, link direction, a foreign key on one side — and a force layout can only express distance.

A picture of the declarations alone describes intent. Held against what the store's records are actually typed as, it describes reality, and the gap is the finding: a declared type at zero instances is something nobody used, a node_type nothing declares is the reverse, and on a real brain it is most of them. Which of the four things this page has to say — the source cannot be asked, nothing is saved, a schema is saved and unused, a schema is in use — is OntologyReport.State, decided in Go so the page never reads an empty list and guesses.

That page has a second half, because on a real brain the first half is a dead end: nothing is declared, so the diagram is empty and the finding is "nobody has modelled this store". Source.Draft answers the question that leaves — what *could* be declared — by deriving a first schema from the store's own vocabulary and drawing it through the same lanes, the same curves and the same overlay, so the counts under the boxes are real and the band outside the model is exactly what the deriver bucketed out or withheld. It arrives as an OntologyDraftView, which is an OntologyReport plus the half a draft has and a saved schema does not: the questions a person has to answer before signing it, and the threshold the drawing was pruned at. Nothing on that path writes — OntologyDrafted is a proposal, saving is a person's act done through ontology_save — and its three states (OntologyDrafted, OntologyNothingToDraft, OntologyUndraftable) share no word with the saved four, so a draft can never be handed a saved schema's sentence.

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 (
	// OntologyUnreadable is a source that keeps no ontology hook, or a read
	// that failed. It is not an absence of ontology — nobody asked.
	OntologyUnreadable = "unreadable"
	// OntologyAbsent is the answer, not the lack of one: this store was asked
	// and holds no schema. The likely state of a real brain today, and the
	// reason the undeclared side of the report is still worth drawing without
	// one — a store with no ontology still has a vocabulary, it just never
	// wrote it down.
	OntologyAbsent = "absent"
	// OntologyUnused is a schema that exists and describes nothing here: every
	// declared type at zero instances. Claimable only when the store's own
	// vocabulary could be read, because otherwise every count is zero for the
	// uninteresting reason.
	OntologyUnused = "unused"
	// OntologyLive is a schema that exists and has not been shown to be
	// unused. Whether anything conforms to it is Usage.Available's question,
	// not this one: with the second reading taken, live means at least one
	// declaration is in use; without it, live means only that there is a
	// schema. Collapsing the two here would let ?gap=0 — which reads nothing
	// about the data — report a store as conforming.
	OntologyLive = "live"
)

The four things this page can have to say before it draws anything, decided here rather than guessed from an empty list on the page. They are ordered from "we know nothing" to "we know everything", and the first three are the ones a real machine is actually in.

View Source
const (
	// OntologyUndraftable is a source that keeps no draft hook, or a
	// derivation that failed. Today this is the shared brain: v2.93.0 has no
	// ontology_draft tool and says so. It is not an absence of vocabulary —
	// nobody could ask.
	OntologyUndraftable = "undraftable"
	// OntologyNothingToDraft is the answer, not the lack of one: the store was
	// read and holds no nodes, so there is no vocabulary to write down. A
	// store whose every type is unclassified is NOT this — that is a drafted
	// finding, and a loud one.
	OntologyNothingToDraft = "nothing-to-draft"
	// OntologyDrafted is a derivation that produced something. It says nothing
	// about whether the something is any good; that is what the decisions are
	// for.
	OntologyDrafted = "drafted"
)

The three things this half of the page can have to say, decided in Go for the same reason the saved four are: an empty list of object types is produced by a source that cannot be asked, by a store with nothing in it, and by a threshold set too high, and telling a reader the wrong one of those is worse than telling them nothing.

Deliberately disjoint from OntologyAbsent / OntologyUnreadable / ... A draft and a saved schema share a renderer and must never share a word: if these were spelled the same the page's own switch would be the place a draft acquired a saved schema's sentence.

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.

View Source
const OntologyDraftDefaultMin = 3

OntologyDraftDefaultMin is the threshold the page opens at.

Not zero, which is the deriver's own default and the right one for a tool whose caller is a program. On the real shared brain zero yields 124 object types, 233 link types and 272 decisions: a page nobody reviews, which teaches a reader that this page is not for reviewing. Three yields 36 / 30 / 117, which is an afternoon. The number is a choice about a person's attention rather than about the data, so it is stated on the page and changed from the URL.

View Source
const OntologyInterval = 30 * time.Second

OntologyInterval is how often the ontology page re-reads.

Slower again than ContractInterval, and for the same kind of reason one step further along. A contract tally moves when somebody reviews a record; a schema moves when somebody redesigns the model, which is a change measured in releases. Half a minute is already far faster than the thing being watched, and the read is not free: the store's vocabulary is two aggregate scans, the same cost class as the contract's.

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 OntologyDecision added in v2.99.0

type OntologyDecision struct {
	Target string `json:"target"`
	// Detail is the question in a sentence; Evidence is what was observed, so
	// a reader can decide without going back to the graph. Both are the
	// deriver's own words: it knows why it asked and a view paraphrasing it
	// would be a second opinion pretending to be the first.
	Detail   string `json:"detail"`
	Evidence string `json:"evidence,omitempty"`
}

OntologyDecision is one question the data cannot answer, as the page shows it. A flattened copy of cortexdb.OntologyDraftDecision rather than the type itself, so the wire shape of this page is this package's to keep.

type OntologyDecisionGroup added in v2.99.0

type OntologyDecisionGroup struct {
	Kind     string `json:"kind"`
	Title    string `json:"title"`
	Question string `json:"question"`
	// Count is how many there are; Decisions may be shorter — see
	// ontologyDecisionLimit.
	Count     int                `json:"count"`
	Decisions []OntologyDecision `json:"decisions"`
	Truncated bool               `json:"truncated,omitempty"`
}

OntologyDecisionGroup is one kind of question, with its count.

Grouped because seven merge candidates and one guessed primary key are two very different amounts of reading and a flat list of a hundred and seventeen communicates neither. Title and Question travel with the group rather than living on the page, for the same reason the rulebook travels with the deriver's report: a heading like "cardinality_suspicion" is a verdict asking to be trusted, and the sentence that makes it actionable belongs beside it.

type OntologyDraftQuery added in v2.99.0

type OntologyDraftQuery struct {
	// SchemaID names the draft. Nothing is saved under it; it is what the
	// header calls the thing and what a later ontology_diff would compare.
	SchemaID string
	// MinNodes and MinEdges keep small types out of the drawing. They never
	// keep anything out of the counts.
	MinNodes int
	MinEdges int
}

OntologyDraftQuery is what the page asks for.

No Usage field, unlike OntologyQuery. Deriving a draft reads the type counts as part of deriving it, so there is no overlay to switch off and nothing saved by switching one off — the ?gap=0 bargain has nothing to buy here.

type OntologyDraftView added in v2.99.0

type OntologyDraftView struct {
	OntologyReport

	// Draft is always true on this payload. The page reads it before anything
	// else and never infers "this is a draft" from a state it does not
	// recognise — a payload whose draftness depended on an enum match would
	// render as a saved schema the first time somebody added a state.
	Draft bool `json:"draft"`

	// The threshold this draft was actually derived at, so the page names the
	// number a reader is being invited to disagree with rather than the one it
	// hoped was used.
	MinNodes int `json:"min_nodes"`
	MinEdges int `json:"min_edges"`
	// What the threshold kept out of the drawing. Counted off the deriver's
	// own findings — it marks them withheld:below-threshold — and never
	// recomputed, so the note under the diagram cannot disagree with the
	// report that produced it.
	PrunedNodeTypes int `json:"pruned_node_types"`
	PrunedNodes     int `json:"pruned_nodes"`
	PrunedEdgeTypes int `json:"pruned_edge_types"`
	PrunedEdges     int `json:"pruned_edges"`

	// What was read, from the deriver's own accounting: the denominator every
	// number above is a part of.
	SourceNodes     int            `json:"source_nodes"`
	SourceEdges     int            `json:"source_edges"`
	SourceNodeTypes int            `json:"source_node_types"`
	SourceEdgeTypes int            `json:"source_edge_types"`
	Buckets         map[string]int `json:"buckets,omitempty"`
	// DerivedAt is when the deriver ran, so a slow draft does not look like a
	// stuck one. Milliseconds, like OntologyReport.At.
	DerivedAt int64 `json:"derived_at,omitempty"`

	Decisions      []OntologyDecisionGroup `json:"decisions"`
	DecisionsTotal int                     `json:"decisions_total"`
	// Notes are the deriver's own words about what it did and did not do —
	// that nothing was saved, that no data type was inferred. They are the
	// caveats on the picture and belong under it, not in a JSON blob.
	Notes []string `json:"notes"`

	// Held against a schema somebody did save, when there is one. On a store
	// with no ontology these are empty and the page says nothing: a diff
	// against nothing would render as "every type added", which reads as a
	// change somebody made.
	Against        string                    `json:"against,omitempty"`
	AgainstVersion int                       `json:"against_version,omitempty"`
	Changes        []cortexdb.OntologyChange `json:"changes,omitempty"`
	ChangesTotal   int                       `json:"changes_total,omitempty"`
	Breaking       bool                      `json:"breaking,omitempty"`
}

OntologyDraftView is a draft as the review page reads it.

It embeds OntologyReport rather than restating it, which is the whole design in one line: the drawing half of a draft and the drawing half of a saved schema are the same fields, so they are the same JSON and the same renderer, and everything below the embed is the half a draft has and a saved schema does not.

type OntologyInterfaceNode added in v2.94.0

type OntologyInterfaceNode struct {
	APIName     string         `json:"api_name"`
	DisplayName string         `json:"display_name,omitempty"`
	Description string         `json:"description,omitempty"`
	Extends     []string       `json:"extends,omitempty"`
	Properties  []OntologyProp `json:"properties"`
	// Implementors is resolved through Extends transitively, so an object type
	// implementing a child interface is listed under the parent it inherits
	// from too. Reading Implements literally would answer a narrower question
	// than the one the page is asking, which is "which object types share this
	// shape".
	Implementors []string `json:"implementors"`
}

OntologyInterfaceNode is one declared interface, with the object types that implement it.

type OntologyLinkEdge added in v2.94.0

type OntologyLinkEdge struct {
	APIName     string          `json:"api_name"`
	Description string          `json:"description,omitempty"`
	Status      string          `json:"status,omitempty"`
	A           OntologyLinkEnd `json:"a"`
	B           OntologyLinkEnd `json:"b"`
	// Multiplicity is the pair read as one phrase — "one-to-many" and the
	// rest. Composed here because it is a fact about the two sides together
	// and a page recomposing it from two cardinality strings would be the
	// second place in this repository that knows the rule.
	Multiplicity string `json:"multiplicity"`
	// Instances is how many edges carry this link type. Same caveat as
	// OntologyObjectNode.Instances: only meaningful when Usage.Available.
	Instances int `json:"instances"`
}

OntologyLinkEdge is one declared link type as the page draws it.

type OntologyLinkEnd added in v2.94.0

type OntologyLinkEnd struct {
	APIName     string `json:"api_name"`
	DisplayName string `json:"display_name,omitempty"`
	ObjectType  string `json:"object_type"`
	Cardinality string `json:"cardinality"`
	ForeignKey  string `json:"foreign_key,omitempty"`
}

OntologyLinkEnd is one side of a link type.

Kept as two sides rather than folded into a single direction because that is how the schema models it and because folding loses the thing worth seeing: multiplicity is per side, and the ONE side is the side that carries the foreign key. A reader who only sees "one-to-many" cannot tell which end holds the column.

type OntologyObjectNode added in v2.94.0

type OntologyObjectNode struct {
	APIName       string         `json:"api_name"`
	DisplayName   string         `json:"display_name,omitempty"`
	Description   string         `json:"description,omitempty"`
	PrimaryKey    string         `json:"primary_key,omitempty"`
	TitleProperty string         `json:"title_property,omitempty"`
	Status        string         `json:"status,omitempty"`
	Visibility    string         `json:"visibility,omitempty"`
	Implements    []string       `json:"implements"`
	Aliases       []string       `json:"aliases,omitempty"`
	Properties    []OntologyProp `json:"properties"`
	// Instances is how many nodes in the store carry this type, and is
	// meaningful only when Usage.Available. A type with none is a declaration
	// nobody has used; the page must not say that of a count it never took.
	Instances int `json:"instances"`
}

OntologyObjectNode is one declared object type as the page draws it.

type OntologyProp added in v2.94.0

type OntologyProp struct {
	APIName     string `json:"api_name"`
	DisplayName string `json:"display_name,omitempty"`
	Kind        string `json:"kind,omitempty"`
	Required    bool   `json:"required,omitempty"`
	Searchable  bool   `json:"searchable,omitempty"`
	Vectorized  bool   `json:"vectorized,omitempty"`
}

OntologyProp is one declared property, flattened for display.

Kind is the data type's discriminator only. An array's element type and a struct's fields are a tree, and a page drawing tens of types has no room to unfold one per property — the discriminator is what a reader is scanning for ("is this a string or a timestamp"), and the nesting is detail the schema JSON still holds for whoever needs it.

type OntologyQuery added in v2.94.0

type OntologyQuery struct {
	// SchemaID picks one of several saved schemas. Empty takes the active one,
	// falling back to the first by id — a choice, not a coin toss, so two
	// loads of one store draw the same page.
	SchemaID string
	// Usage requests the store's own vocabulary alongside the declarations.
	// False is the ?gap=0 page: the declarations alone, and nothing read for
	// the overlay — the same bargain the contract panel makes when it is
	// folded, which is that work nobody is looking at does not get done.
	Usage bool
}

OntologyQuery is what the page asks for.

type OntologyReport added in v2.94.0

type OntologyReport struct {
	Available bool   `json:"available"`
	Reason    string `json:"reason,omitempty"`
	// State is one of the four constants above, decided here.
	State string `json:"state"`

	// Saved reports whether the store holds any schema at all — the question
	// behind OntologyAbsent, kept as its own field so the page never has to
	// infer it from an empty ObjectTypes.
	Saved   bool                `json:"saved"`
	Schemas []OntologySchemaRef `json:"schemas"`

	SchemaID      string `json:"schema_id,omitempty"`
	Name          string `json:"name,omitempty"`
	Description   string `json:"description,omitempty"`
	Version       int    `json:"version,omitempty"`
	Active        bool   `json:"active,omitempty"`
	Enforcement   string `json:"enforcement,omitempty"`
	StrictActions bool   `json:"strict_actions,omitempty"`
	// Actions and ObjectSets are counted, not listed: they describe how the
	// data is written and read rather than what shape it has — the same line
	// DiffOntologySchemas draws when it declines to compare them — so they
	// belong on this page as evidence the schema has them, not as a second
	// diagram.
	ActionTypes      int `json:"action_types"`
	ObjectSets       int `json:"object_sets"`
	SharedProperties int `json:"shared_properties"`

	ObjectTypes []OntologyObjectNode    `json:"object_types"`
	LinkTypes   []OntologyLinkEdge      `json:"link_types"`
	Interfaces  []OntologyInterfaceNode `json:"interfaces"`

	// DeclaredUnusedTypes and DeclaredUnusedLinks are how many declarations
	// nothing in the store uses. Counted here rather than on the page so the
	// sentence and the diagram cannot disagree about what "unused" means.
	DeclaredUnusedTypes int `json:"declared_unused_types"`
	DeclaredUnusedLinks int `json:"declared_unused_links"`

	Usage OntologyUsage `json:"usage"`

	// At is when this was read, so a slow number does not look like a stuck
	// one.
	At int64 `json:"at"`
}

OntologyReport is what the ontology page draws.

Available and State carry the honesty, and they are not the same question. Available answers "could this view be asked at all"; State answers "and what did it find". A source with no ontology hook, a store with no schema, and a schema nothing conforms to are three different findings, and a page handed only an empty list of object types would render all three as the same empty diagram.

type OntologySchemaRef added in v2.94.0

type OntologySchemaRef struct {
	SchemaID string `json:"schema_id"`
	Name     string `json:"name,omitempty"`
	Version  int    `json:"version"`
	Active   bool   `json:"active"`
}

OntologySchemaRef names one saved schema, so a page drawing one of several can say which and offer the others.

type OntologyStrayType added in v2.94.0

type OntologyStrayType struct {
	// Name is the node_type or edge_type verbatim, empty for the records that
	// carry none — which is a finding of its own and not the same as a type
	// the schema is missing.
	Name  string `json:"name"`
	Count int    `json:"count"`
}

OntologyStrayType is one type present in the store that the ontology never declared.

type OntologyUsage added in v2.94.0

type OntologyUsage struct {
	// Available separates a count that came back zero from a count nobody
	// took. Without it a page has no way to tell "nothing uses this type"
	// from "this view cannot count", and would report the second as the first.
	Available bool   `json:"available"`
	Reason    string `json:"reason,omitempty"`
	// Scope says in words what was counted, because the two source shapes
	// count different sets: the library counts every row including chunks, a
	// shared brain answers over the entity graph with chunks excluded. Two
	// totals that disagree are not a fault and a reader should not have to
	// guess that.
	Scope string `json:"scope,omitempty"`

	Nodes int `json:"nodes"`
	Edges int `json:"edges"`
	// NodeTypes and EdgeTypes are how many distinct types the store uses,
	// declared or not — the denominator the undeclared lists are a part of.
	NodeTypes int `json:"node_types"`
	EdgeTypes int `json:"edge_types"`

	// UndeclaredNodes and UndeclaredEdges are the types in the data that no
	// object or link type describes, largest first: on a store with no schema
	// this is the whole vocabulary, and it is the page's only content.
	UndeclaredNodes []OntologyStrayType `json:"undeclared_nodes"`
	UndeclaredEdges []OntologyStrayType `json:"undeclared_edges"`
	// The true counts behind the capped lists above.
	UndeclaredNodeTypes int  `json:"undeclared_node_types"`
	UndeclaredEdgeTypes int  `json:"undeclared_edge_types"`
	UndeclaredNodeCount int  `json:"undeclared_node_count"`
	UndeclaredEdgeCount int  `json:"undeclared_edge_count"`
	NodeListTruncated   bool `json:"node_list_truncated,omitempty"`
	EdgeListTruncated   bool `json:"edge_list_truncated,omitempty"`
}

OntologyUsage is the second reading: what the store's records are actually typed as, against which the declarations are held.

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 New added in v2.94.0

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

New reads the graph once, starts polling, and returns a view that serves on a mux somebody else owns — see Handler.

It exists because Start binds its own loopback listener, which is right for the MCP server (the view is a thing you open, and a port is how you open it) and wrong for a process that already has an HTTP server and wants the graph as one page of it. That process should not have to run a second listener on a second port for one route. Everything Start did apart from listening is here, and Start is now New plus a listener.

The pages fetch their APIs by relative path ("api/graph", href="ontology"), so the handler can be mounted under a prefix — with http.StripPrefix and a trailing slash on the mount — and the browser resolves the rest.

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 on its own loopback listener, 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) Handler added in v2.94.0

func (s *Server) Handler() http.Handler

Handler is the view's routes, rooted at "/". Mount it wherever the page should live; the pages' own links and fetches are relative, so a prefix works as long as it ends in a slash and is stripped before this handler.

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)
	// Ontology answers what this store is allowed to talk about — the object
	// types and link types it declares — and, when asked for, what its records
	// are actually typed as, so the two can be held against each other.
	//
	// Optional and nil for the same reason Contract is, and with a sharper
	// consequence: a nil hook must never render as "no ontology is saved". A
	// store nobody can ask and a store nobody has modelled are different
	// findings, and the second is far and away the more common one — so
	// [OntologyReport.State] names which, and the page reads that rather than
	// inferring it from an empty list of types.
	Ontology func(ctx context.Context, q OntologyQuery) (OntologyReport, error)
	// Draft derives an ontology this store does not have — what *could* be
	// declared, held against what is there, with the questions a person has
	// to answer beside it. It saves nothing.
	//
	// A hook of its own rather than a flag on OntologyQuery, because nil is
	// the answer to a different question. Ontology non-nil means "this source
	// can be asked what it declares"; the shared brain answers that today and
	// cannot derive a draft, because ontology_draft postdates the version it
	// runs. One nil check cannot carry two answers, and folding the two would
	// leave a source that can read a schema but not derive one with no way to
	// say so — which is the state the cluster is actually in.
	Draft func(ctx context.Context, q OntologyDraftQuery) (OntologyDraftView, 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.

func SourceFor added in v2.96.0

func SourceFor(db *cortexdb.DB, describe string) *Source

SourceFor is a Source over a brain the caller already holds open.

OpenSource opens its own database from the environment, which is right for the MCP server and the command line and wrong for a process that has a *cortexdb.DB in hand and wants the view to read that one — opening the file a second time would be a second connection to a database the process is writing through the first. The Source it returns does not close the DB: it was not the one that opened it.

Jump to

Keyboard shortcuts

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