runbook

package
v1.4.7 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package runbook is the runbook-RAG corpus store and ingestion (write) path for the SRE agent's read-only find_runbook tool. It is deliberately SEPARATE from pkg/agent/ai/analyze/tools: the tool reads a vector index through a narrow local interface, while this package owns the write path (ingest + persist). Keeping the write path out of the tools package is what keeps the analyze import-graph guard green (a write in that package would trip the read-only guard).

Persistence goes through storage.Provider (ReadBlob/WriteBlob), the same seam the pattern catalog and shadow log use — never os.WriteFile. Every record carries an OrgID (default storage.DefaultOrgID) so the enterprise org-injection seam scopes runbooks per-tenant with zero OSS change.

Index

Constants

View Source
const BlobName = "runbooks"

BlobName is the storage blob key the runbook corpus is persisted under. Backends translate it into a path / redis key / row.

View Source
const SourceSubdir = "runbooks"

SourceSubdir is the subdirectory, under the storage data folder, where operators place their *.md runbook files for ingestion. The directory is <data folder>/runbooks (e.g. ./data/runbooks; /app/data/runbooks in the container image); the server auto-ingests it at boot.

Variables

View Source
var ErrNotFound = errors.New("runbook: not found")

ErrNotFound is returned by the manager's CRUD methods when no runbook exists under the given ID.

Functions

func IngestDir

func IngestDir(ctx context.Context, store *Store, embedder core.Embedder, dir, orgID string) (int, error)

IngestDir scans dir (recursively) for `*.md` runbook files, embeds each one through embedder, and writes the resulting records to store, then persists the corpus. It is the WRITE path — it lives here in pkg/runbook, OUTSIDE pkg/agent/ai/analyze/tools, so the analyze read-only import-graph guard stays green.

Ingestion is incremental: a runbook whose indexed text is unchanged since the last ingest reuses its cached vector instead of calling the embedder, so re-running ingest (including the server's boot-time auto-ingest) over an unchanged corpus makes no embedding calls. A missing dir is treated as an empty corpus (0 ingested, no error).

orgID scopes every ingested record (blank ⇒ storage.DefaultOrgID). Returns the number of records ingested. The embeddings made here are over operator-authored runbook content, the accepted trust boundary documented in the runbook-RAG docs.

Types

type Manager

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

Manager owns the runbook corpus write path plus a live, swappable search index for the find_runbook tool. It is the single seam the admin runbooks UI (CRUD via the controller) and the agent's read path (find_runbook) share: uploads and deletes mutate the corpus and atomically swap in a freshly-built index, so edits are searchable immediately without a server restart.

The Manager lives in pkg/runbook (the write path), NOT in pkg/agent/ai/analyze/tools — the tool only ever sees the read-only vectorindex.Index returned by Index(), keeping the analyze read-only import-graph guard green.

func NewManager

func NewManager(store *Store, embedder core.Embedder) *Manager

NewManager builds a manager over an existing corpus store and an optional embedder. It snapshots the current corpus into the live index so reads work immediately. A nil embedder is allowed: CRUD still works, but uploaded runbooks are stored without vectors (not searchable until embeddings are configured and the corpus re-ingested).

func (*Manager) Delete

func (m *Manager) Delete(id string) error

Delete removes the runbook stored under id, persists the corpus, and rebuilds the live index. Returns ErrNotFound when no such runbook exists.

func (*Manager) Embedder

func (m *Manager) Embedder() core.Embedder

Embedder returns the manager's query embedder (may be nil). The find_runbook tool uses it to embed search queries against the same model the corpus was embedded with.

func (*Manager) Get

func (m *Manager) Get(id string) (Record, error)

Get returns the runbook stored under id, or ErrNotFound.

func (*Manager) HasEmbedder

func (m *Manager) HasEmbedder() bool

HasEmbedder reports whether the manager can embed uploads. When false, uploads persist without vectors (not searchable yet).

func (*Manager) Index

func (m *Manager) Index() vectorindex.Index

Index returns the read-only search seam over the manager's live index. The find_runbook tool consumes this; every mutation atomically swaps a freshly-built index underneath it, so searches always see the latest corpus.

func (*Manager) IngestDir

func (m *Manager) IngestDir(ctx context.Context, dir, orgID string) (int, error)

IngestDir scans dir for `*.md` runbooks and ingests them through the shared write path, then rebuilds the live index. It is the boot-time auto-ingest entry point. With no embedder configured it is a no-op (find_runbook is disabled, so there is nothing to embed). A missing dir is treated as an empty corpus.

func (*Manager) List

func (m *Manager) List() []Record

List returns every runbook record, ordered by ID.

func (*Manager) Upload

func (m *Manager) Upload(ctx context.Context, files []UploadFile, orgID string) (int, error)

Upload ingests one or more uploaded `.md` runbook files: each is parsed (honoring optional YAML front-matter), embedded (when an embedder is configured), upserted, and persisted, then the live index is rebuilt so the new runbooks are immediately searchable. Re-uploading a file with the same basename replaces the existing runbook. Returns the number of runbooks written.

type Record

type Record struct {
	ID string `json:"id"`
	// OrgID scopes the runbook to one organization. Defaults to
	// storage.DefaultOrgID ("default") so single-tenant OSS users never
	// see or set it; enterprise multi-tenant routing reads it to isolate
	// per-org corpora.
	OrgID     string    `json:"org_id,omitempty"`
	Title     string    `json:"title"`
	Services  []string  `json:"services,omitempty"`
	Tags      []string  `json:"tags,omitempty"`
	Body      string    `json:"body"`
	Source    string    `json:"source,omitempty"`
	UpdatedAt time.Time `json:"updated_at"`
	Vector    []float32 `json:"vector,omitempty"`
	// ContentHash is the SHA-256 of the indexed text (title + body) at
	// embed time. Auto-ingest compares it to skip re-embedding runbooks
	// whose content is unchanged, so a reboot with no edits makes no
	// embedding calls.
	ContentHash string `json:"content_hash,omitempty"`
}

Record is the durable shape of one runbook in the corpus. The cached Vector is the embedding of the runbook's indexed text, stored alongside the record so the corpus never needs re-embedding at boot.

type Store

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

Store is the in-memory + blob-backed runbook corpus. All methods are safe for concurrent use. The read side (All / BuildIndex) is consumed at boot to build the vector index; the write side (Upsert / Persist) is the ingestion path.

func LoadStore

func LoadStore(store storage.Provider) (*Store, error)

LoadStore opens the existing runbook blob from the storage provider, or returns an empty store when none exists (a fresh corpus). A nil provider yields an empty, memory-only store.

func (*Store) All

func (s *Store) All() []Record

All returns every record, ordered by ID for determinism.

func (*Store) BuildIndex

func (s *Store) BuildIndex(maxDocs int) *vectorindex.Memory

BuildIndex constructs an in-memory cosine index from the cached vectors in the corpus. maxDocs bounds the index size. Records without a cached vector are skipped (they were never embedded). The returned index is read-only and safe to hand to the find_runbook tool's searcher bridge. An empty corpus yields a non-nil, empty index so the tool registers and cleanly reports Found:false.

func (*Store) Delete

func (s *Store) Delete(id string) bool

Delete removes the record stored under id and reports whether a record was present. It is the delete side of the admin CRUD path; callers Persist() afterwards to make the removal durable.

func (*Store) Get

func (s *Store) Get(id string) (Record, bool)

Get returns the record stored under id, if any. It is the read side of the admin CRUD path (the manager surfaces it to the runbooks UI).

func (*Store) Len

func (s *Store) Len() int

Len reports the number of records in the corpus.

func (*Store) Persist

func (s *Store) Persist() error

Persist writes the whole corpus to the storage backend via WriteBlob. A nil provider is a no-op (memory-only store). This is part of the write path.

func (*Store) Upsert

func (s *Store) Upsert(rec Record)

Upsert inserts or replaces a record by ID. A blank OrgID is normalized to storage.DefaultOrgID. This is the write path; it is called by ingestion, never by the read-only tool.

type UploadFile

type UploadFile struct {
	Name    string
	Content []byte
}

UploadFile is one operator-uploaded runbook: the original filename and its raw `.md` bytes. The filename (basename only) becomes the runbook's stable ID and Source, so re-uploading a file with the same name replaces the existing runbook.

Directories

Path Synopsis
Package vectorindex is the swappable vector-search seam behind the runbook-RAG find_runbook tool.
Package vectorindex is the swappable vector-search seam behind the runbook-RAG find_runbook tool.

Jump to

Keyboard shortcuts

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