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.
What is drawn is coloured by node type, which answers "what is in here". The other question a person brings to a picture of a brain is which of it anybody checked, and the knowledge contract answers that per record — so Node and Edge carry the record's `_grade` and the page has a switch between the two palettes. Type is the default and stays it. Grade mode draws the contract's five in the product's own colours, lifted for this background (see grade.go), with GradeLegend as the legend — an unexplained colour scheme is decoration — and it colours the relations as well as the things, because a graph's assertions are mostly edges. A source whose reads do not carry grades sets Source.Grades false and the legend says so, which is a different finding from a store on which nothing is graded.
Selecting a node or a relation opens the inspector, which is the fourth route onto the page and the one that joins the picture to the product: the record's source file and chunk, its producer, its grade and the reason for it, when the fact became true, the text it was drawn from, what it was recorded as contradicting, and the decision somebody signed against it — every id among them a link, so a reader can walk from a fact to what disagrees with it without going back to the search box. It arrives through Source.Record as a RecordDetail, fetched once per click rather than polled, which is what makes it affordable to answer that expensively. Nil is a legitimate answer here too, and there are three states rather than two: the source cannot be asked, the source looked and the shelf no longer holds this, and here is the record.
The page can also be pinned to an instant. Source.ReadAsOf reads the graph as it stood then, through the same as-of machinery every other past read in the module uses, and the page says so in a banner that cannot be folded away — a graph quietly showing last week is worse than one that cannot show it at all. Live updates stop while pinned, and stop on the server: a pinned stream never subscribes to the poller, so there is no delta to arrive. Per connection, not per server, because one reader looking at last Tuesday must not freeze the page of the reader beside them.
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
- func ClipLabel(s string) string
- func GradeColor(grade string) string
- func LoadLocal(ctx context.Context, sqlDB *sql.DB) ([]Node, []Edge, error)
- func LoadRemote(ctx context.Context, addr, token string, limit int, quiet bool) ([]Node, []Edge, error)
- func OpenInBrowser(url string) error
- func ParseAsOf(raw string) (time.Time, error)
- func PortFromEnv() int
- func RemoteConfigured() (addr, token string, ok bool)
- type ContractReport
- type ContractRow
- type Delta
- type Edge
- type Event
- type GradeLegendEntry
- type Hub
- type Message
- type Node
- type OntologyDecision
- type OntologyDecisionGroup
- type OntologyDraftQuery
- type OntologyDraftView
- type OntologyInterfaceNode
- type OntologyLinkEdge
- type OntologyLinkEnd
- type OntologyObjectNode
- type OntologyProp
- type OntologyQuery
- type OntologyReport
- type OntologySchemaRef
- type OntologyStrayType
- type OntologyUsage
- type Payload
- type RecordDecision
- type RecordDetail
- type RecordText
- type Server
- func Current() *Server
- func CurrentFor(key string) *Server
- func New(ctx context.Context, src *Source, interval time.Duration, activity bool) (*Server, error)
- func Shared(ctx context.Context, activity bool) (*Server, error)
- func SharedFor(ctx context.Context, key string, activity bool, ...) (*Server, error)
- func Start(ctx context.Context, src *Source, port int, interval time.Duration, ...) (*Server, error)
- type Snapshot
- type Source
Constants ¶
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.
const ( // GradeColorVerified lifts #047857 (emerald-700) to emerald-400. GradeColorVerified = "#34d399" // GradeColorSelfConsistent lifts #0e7490 (cyan-700) to sky-400. GradeColorSelfConsistent = "#38bdf8" // GradeColorAsserted lifts #8f9bb0 (a cool grey) to slate-400. Asserted is // the shoulder-shrug of the five — a source said so and nothing checked it // — and it must not look like an achievement. GradeColorAsserted = "#94a3b8" // GradeColorHeld lifts #b45309 (amber-700) to amber-400. GradeColorHeld = "#fbbf24" // GradeColorRefused lifts #b42318 (red-700) to red-400. GradeColorRefused = "#f87171" // GradeColorUntagged is the dim grey for a record carrying no _grade at // all. Deliberately darker than asserted and desaturated to nothing: it is // the shape of the shelf rather than a judgement, and on a real brain it // is most of what is drawn, so a bright colour here would drown the five. GradeColorUntagged = "#3f4d66" // GradeColorUnknown is a _grade the contract does not define — a producer // writing something wrong. Rose, and the only colour here that is not on // the contract's ladder at all, because it is somebody's bug and the page // must not let it hide among the values the contract does define. It is // the same rose the contract panel already marks an unknown row with. GradeColorUnknown = "#fb7185" )
The contract's palette, lifted for a dark ground.
The product's own contract colours are chosen for a light background: verified #047857, self_consistent #0e7490, asserted #8f9bb0, held #b45309, refused #b42318. On #04060d they are all but invisible — #047857 against this page's background is a contrast ratio under 2, and #b42318 reads as a dark smudge rather than as an alarm. Worse, the scene bloom-passes the rendered frame, so a dark fill does not merely read badly, it drops out of the glow entirely and a refused node becomes the one thing on the page you cannot see.
So each is lifted to roughly the same hue at a much higher lightness — the Tailwind-600 originals taken to their 400 step, which is the same ramp the rest of this page's colours already come off — and the ordering between them is preserved: verified is still the green, refused is still the red, and asserted is still the one that says nothing much. A reader who knows the light palette recognises these; a reader who does not still reads green as settled and red as refused.
const ( KindQuery = "query" KindWrite = "write" KindRelate = "relate" )
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.
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.
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".
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.
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.
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.
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 ¶
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 GradeColor ¶ added in v2.101.0
GradeColor is the colour for one record's grade, and the same decision the contract panel's gradeColor makes: a value the contract defines gets its place on the ladder, no value at all gets the dim grey, and anything else gets the rose that says somebody is writing a word nobody agreed on.
func LoadLocal ¶
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.
The nodes and edges come back without a grade: graph_list_all carries an id, a label and a type and nothing else, and adding a field to that response is a change to the shared brain's wire, not to this page. So a remote source declares Grades false and the page says the grades cannot be read here — which must not be confused with a shelf on which nothing is graded.
func OpenInBrowser ¶
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 ParseAsOf ¶ added in v2.101.0
ParseAsOf reads the instant a page or an embedder pinned itself to.
Two spellings, because two different things write this parameter. The page sends unix milliseconds, which is what a datetime field yields and what survives a round trip through JavaScript without a timezone argument. A person putting ?as_of= in the address bar writes RFC 3339, because that is what the rest of this store's timestamps look like and typing a millisecond count by hand is not a thing anybody does.
An unparseable value is an error rather than a fallback to now. Everywhere else on this page a bad parameter falls back — a page is not worth a 400 — but "now" is the one wrong answer this parameter must never give: the whole point of the pin is that the reader is told they are looking at the past, and silently showing them the present under that banner is worse than showing them nothing.
func PortFromEnv ¶
func PortFromEnv() int
func RemoteConfigured ¶
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 and AddedEdges carry upsert semantics rather than insert: a record whose label, type or grade 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.
For an edge that used to be a distinction without a difference — an edge was its two ends and its label, all three of which are its identity, so nothing could change without it becoming a different edge. It stopped being one when an edge started carrying its grade: a record reviewed from held to verified is the same edge saying something new about itself, and a view that only learned about edges appearing and disappearing would have gone on drawing last week's verdict.
type Edge ¶
type Edge struct {
Source string `json:"source"`
Target string `json:"target"`
Label string `json:"label"`
// ID is the edge's own row id, carried so the inspector can be asked about
// it. An edge named only by its two ends and a label is not a record
// anything can look up — and it is the record that has the source, the
// chunk and the grade on it.
//
// Not part of the edge's identity for diffing: two nodes can be joined by
// several relations and edgeKey tells those apart by type, which is a
// question about the graph rather than about the row.
ID string `json:"id,omitempty"`
// Grade is the edge's own _grade. An edge is an assertion about two
// things and carries the contract exactly as a node does — a picture that
// graded only the nodes would report a shelf far better established than
// it is, which is the argument graph.PropertyCount already makes.
Grade string `json:"grade,omitempty"`
}
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 ¶
ClassifyToolCall turns one MCP tool call into an event for the live view, reporting false for calls the view should ignore.
type GradeLegendEntry ¶ added in v2.101.0
type GradeLegendEntry struct {
Grade string `json:"grade"`
Color string `json:"color"`
Means string `json:"means"`
}
GradeLegendEntry is one line of the grade legend: a colour, the word the contract uses for it, and what that word means in a sentence short enough to sit in a panel.
The sentence is here rather than in the page because it is the contract's meaning and not the page's styling, and because a legend that only names the five teaches nobody the difference between asserted and self_consistent — which is the one distinction a reader of this picture has to make.
func GradeLegend ¶ added in v2.101.0
func GradeLegend() []GradeLegendEntry
GradeLegend is the legend the page draws in grade mode: the contract's five in the order they stand, then the two rows that are not grades.
Ordered here rather than in the page for the reason ContractReport.Rows is: the contract's ladder of standing is Go's to know, and a page that sorted the five itself would be a second place to keep it.
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.
type Node ¶
type Node struct {
ID string `json:"id"`
Label string `json:"label"`
Type string `json:"type"`
// Grade is the record's _grade: by what kind of thing its truth is
// established. One of the knowledge contract's five closed values, or
// empty for a record no producer stamped — which on a real shelf is most
// of them, and is a finding rather than a gap.
//
// omitempty on purpose: a source that reports no grades puts the same
// bytes on the wire it did before this field existed.
Grade string `json:"grade,omitempty"`
}
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"`
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"`
// Grades says the nodes and edges above carry a _grade, so the page may
// offer to colour by it. False is not "nothing is graded" — it is "this
// source does not report grades", and the legend says so rather than
// painting a brain the untagged grey.
Grades bool `json:"grades,omitempty"`
// Records says a record can be looked up on this source, so the inspector
// may offer to. False leaves the panel saying which of the two it is.
Records bool `json:"records,omitempty"`
// Temporal says this source can be asked about the past.
Temporal bool `json:"temporal,omitempty"`
// AsOf is the instant this payload describes, unix milliseconds, and zero
// for the live graph. Pinned is not derivable from it — a page can ask for
// an instant a source cannot answer — so the two are separate, and the
// banner reads Pinned.
AsOf int64 `json:"as_of,omitempty"`
Pinned bool `json:"pinned,omitempty"`
// PinReason is why a pin was refused, when one was. Empty on both a live
// payload and a successful pin.
PinReason string `json:"pin_reason,omitempty"`
}
Payload is the page's opening state: everything needed to draw, plus what the header reports about where it came from.
type RecordDecision ¶ added in v2.101.0
type RecordDecision struct {
ID string `json:"id"`
Kind string `json:"kind,omitempty"`
Actor string `json:"actor,omitempty"`
At string `json:"at,omitempty"`
Verdict string `json:"verdict,omitempty"`
Note string `json:"note,omitempty"`
// Grade is the decision's own grade. A decision recorded as held is not
// an account of anything yet, and a panel that listed it beside a verified
// one would say the record had been settled when it had not.
Grade string `json:"grade,omitempty"`
// Supersedes is what this decision replaced — ids, and therefore links.
Supersedes []string `json:"supersedes,omitempty"`
}
RecordDecision is one ledger entry standing against a record: who decided what, when, and why.
A projection of cortexdb.DecisionRecord rather than the type itself, because the panel shows six fields and the record has fourteen, and because the two that matter here — whether this decision is the one that *verified* the record, and what it superseded — are read off the entry rather than carried on it.
type RecordDetail ¶ added in v2.101.0
type RecordDetail struct {
Available bool `json:"available"`
Reason string `json:"reason,omitempty"`
Found bool `json:"found"`
ID string `json:"id"`
Edge bool `json:"edge,omitempty"`
Type string `json:"type,omitempty"`
Content string `json:"content,omitempty"`
// From and To are an edge's ends: ids, and therefore links.
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
// The contract's keys, verbatim. Empty means the record does not carry
// the key, which stays distinguishable from carrying an empty value only
// because the store reads them guarded — see graph.RecordsWithProperties.
Grade string `json:"grade,omitempty"`
State string `json:"state,omitempty"`
Why string `json:"why,omitempty"`
Source string `json:"source,omitempty"`
Chunk string `json:"chunk,omitempty"`
Producer string `json:"producer,omitempty"`
At string `json:"at,omitempty"`
By string `json:"by,omitempty"`
// Confidence is an extraction confidence, shown as detail and never as
// evidence: contract.go is explicit that a model's confidence in its own
// output is not evidence about the world.
Confidence string `json:"confidence,omitempty"`
// ValidFrom is when the fact became true in the world, RFC 3339, empty
// when the record carries no validity — which every record written before
// the temporal release does. It is not _at: _at is when the producer made
// the record, and the two differ whenever anything is backdated.
ValidFrom string `json:"valid_from,omitempty"`
// Contradicts names the records this one cannot both-be-true with. Kept
// readable rather than resolved into content, because they are ids and
// therefore links, and because both records are still on the shelf — the
// disagreement was kept, not deleted.
Contradicts []string `json:"contradicts,omitempty"`
// DocumentID and ChunkIDs are where the record came from, and Text is as
// much of that text as the panel is given. MissingChunks is a citation
// pointing at text that has since been deleted, which is exactly the sort
// of thing worth surfacing rather than dropping.
DocumentID string `json:"document_id,omitempty"`
ChunkIDs []string `json:"chunk_ids,omitempty"`
Text []RecordText `json:"text,omitempty"`
MissingChunks []string `json:"missing_chunks,omitempty"`
// Inferred says the record was derived rather than read, and Rule names
// the derivation. The chunks then support the premises, not this record.
Inferred bool `json:"inferred,omitempty"`
Rule string `json:"rule,omitempty"`
// Decisions are the ledger entries about this record, newest first. Empty
// means nobody decided anything about it — which, for a record graded
// verified, is itself worth seeing.
Decisions []RecordDecision `json:"decisions,omitempty"`
// Chain is filled only when the record *is* a decision: the decisions it
// rests on, so a reader can walk back from an action to the facts under
// it without leaving the panel.
Chain []RecordDecision `json:"chain,omitempty"`
// ChainTruncated says the walk stopped with decisions still unvisited. A
// chain that quietly stopped reads as a complete account of why something
// was done, which is the one thing it must never do.
ChainTruncated bool `json:"chain_truncated,omitempty"`
// Notes are things this view could not answer about a record it did find:
// a provenance read that failed, a ledger query that errored. Reported
// rather than swallowed, and rather than failing the whole panel — the
// contract keys are already worth showing without them.
Notes []string `json:"notes,omitempty"`
}
RecordDetail is what the inspector draws.
Available and Reason are the same distinction ContractReport draws and for the same reason: a source that cannot be asked and a record that carries nothing must not render alike. Found is the third state, and it is not the same as either — a node that is on screen but no longer in the store is a real finding about a graph being written under the view.
type RecordText ¶ added in v2.101.0
type RecordText struct {
ChunkID string `json:"chunk_id"`
DocumentID string `json:"document_id,omitempty"`
Content string `json:"content"`
}
RecordText is one piece of the text a record was drawn 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 ¶
CurrentFor returns the running view of one graph, or nil.
func New ¶ added in v2.94.0
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 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) Handler ¶ added in v2.94.0
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) SourceName ¶
SourceName names the brain this view reads, for a caller that wants to say so.
func (*Server) WatchesCalls ¶
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)
// Grades reports whether Read fills in Node.Grade and Edge.Grade.
//
// A field rather than a hook, which is the odd one out here and
// deliberate: every other optional answer on this struct is a separate
// question the source is asked, and a grade is not — it rides on Read, on
// the record the page is already drawing. So what has to be declared is
// not "can you answer this" but "does what you already answer carry it",
// which is a fact about the source rather than a call.
//
// It exists for the same reason the nil hooks do. A source that cannot
// report grades returns every node ungraded, which is indistinguishable on
// the wire from a shelf nobody has stamped — and those are different
// findings. The shared brain is exactly this case: graph_list_all carries
// no grade, so the colour-by-grade mode says it cannot be read here rather
// than painting the whole brain the untagged grey.
Grades bool
// ReadAsOf reads the graph as it stood at an instant, for the page pinned
// to a moment in the past.
//
// Optional, and nil is a legitimate answer: a side graph assembled in
// memory has no history, and a shared brain has one this view cannot reach
// — graph_list_all answers about now and nothing else. A nil hook must
// never render as "the graph was empty then". The page says the source
// cannot be asked about the past, and stays where it is.
//
// A hook of its own rather than an argument to Read, for the reason
// graph.ReadOptions gives about the as-of epoch: it is not a parameter to
// one query but the instant a whole read happens in, and a source that can
// answer about now and not about then must be able to say so.
ReadAsOf func(ctx context.Context, at time.Time) ([]Node, []Edge, error)
// Record answers everything the store can say about one node or edge: its
// source file and chunk, its producer, its grade and why, when it became
// true, the text it was drawn from, what it was recorded as contradicting,
// and what anybody decided about it.
//
// Optional and nil for the same reason Contract is, with the same
// consequence: nil must render as "this view cannot look a record up", not
// as a record that carries nothing. The two are different findings and the
// panel says which — and a third state sits beside them, a record the
// source looked for and did not find, which on a graph being written under
// the view is a real event rather than a fault.
Record func(ctx context.Context, id string) (RecordDetail, 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 ¶
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
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.