deadend

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package deadend answers one question: has this been tried before?

Every other retrieval path here is pull — an agent asks, and the store answers. This one is push. The agent is about to propose something, and the useful move is to interrupt: *you tried that in March, here is why it did not work, here is who found out.*

The distinction matters more than it looks. `resume` gives an arriving agent the last checkpoint, which covers the approach ruled out yesterday on the project it is already working on. It does nothing for the approach ruled out in March, on a project nobody mentioned, by an agent that no longer exists. That knowledge is in the vault — it just has no way of reaching the moment it would have been useful, because nobody thinks to search for the thing they are about to suggest.

So the corpus is every dead end recorded anywhere in the vault, and the query is the proposal itself. This is also the part of the system that cannot be copied: the mechanism is a morning's work, and the two years of accumulated "we tried that" is not.

What counts as a dead end

Checkpoints carry a Failed list, which is the curated corpus — someone chose to write those down as ruled out. Working notes are messier and matter anyway: an agent killed before it could check in leaves its findings only there, and "the audio team vetoed dropping the second mic" is exactly the kind of thing that dies with the session. Those are admitted when they read as a failure, by an explicit marker rather than by sentiment, because a note wrongly filed as a dead end will stop work that should have happened.

Index

Constants

View Source
const (
	MatchedLexically    = "lexical"
	MatchedSemantically = "semantic"
	MatchedBoth         = "lexical and semantic"
)

The values of Ruling.Method.

Variables

View Source
var ErrNoLocalModel = errors.New("no local embedding model is in use")

ErrNoLocalModel is CheckNoting's semanticErr when no embedder is configured. Only a local runtime is ever asked (nothing leaves the machine), so with none the check is lexical — and a paraphrase of a ruled- out approach passes it unseen, which the caller has to be able to say.

Functions

func FirstShowing added in v0.5.2

func FirstShowing(db *sql.DB, sessionID, rel string) (bool, error)

FirstShowing reports whether this is the first time in session that a ruling was shown for rel, and records that it now has been. One line per file per session: an agent editing reader.go twenty times needs the ruling once, and a line repeated on every edit is read as noise and then not read at all.

A cursor, not a record — losing it shows a ruling once more. An empty session cannot be told apart from any other, so it always counts as a first showing.

func MarkDrift added in v0.5.1

func MarkDrift(hits []Ruling, dir string)

MarkDrift notes, on each ruling, whether the files it names have changed in the repository at dir since it was recorded. Only the rulings about to be shown are measured: it costs a git call per named file.

func Matches added in v0.5.0

func Matches(lexical, semantic float64, embedded bool) bool

Matches is the rule both halves of before_you_try apply to a candidate: dead ends here, procedures in internal/procedure, so a proposal is not judged one way against what failed and another against what worked. embedded is whether semantic is a measurement at all. A ruling the embedder never reached, or one whose vector it cannot be compared with, has no second opinion to give, and lexical alone is what the check was before embeddings; it stays that.

func PossiblySuperseded

func PossiblySuperseded(scope Scope, when int64, now time.Time) bool

PossiblySuperseded reports whether a version-bound ruling is old enough that the dependency it names may have moved since. It is a time-based guess, not a check against an actual manifest — Logos has no dependency graph to consult — so it is always surfaced as a caveat, never a silent drop and never a hard "this no longer applies".

func Render

func Render(proposed string, hits []Ruling) string

Render writes the interruption.

The wording is doing real work here, and it is not decoration. This output lands in front of a model that is about to propose something, and there are two ways to get it wrong. Too soft — "you may wish to consider" — and it gets skimmed past. Too hard — "this will not work" — and it stops an agent from retrying something that deserves a second look, which is worse, because a dead end recorded a year ago under different constraints is evidence, not a verdict.

So the shape is: state plainly that it was tried, say by whom and when, and hand the decision back. The agent is told to say out loud that it knows, because the visible version of this — "we tried that in March, the drop test failed at 1.2m" — is the difference between a memory that stores and a memory that helps.

func SemanticSkipped added in v0.4.4

func SemanticSkipped(err error) string

SemanticSkipped is the line that says the check ran on wording alone, or "" when the semantic arm covered everything.

func Shown added in v0.5.2

func Shown(db *sql.DB, sessionID, rel string) (bool, error)

Shown reports whether session has already been shown a ruling for rel, without recording anything: the edit hook asks before it does the work of a line, and records only once the line is printed.

func Similarity added in v0.5.0

func Similarity(a, b []float32) (float64, bool)

Similarity is the cosine of two embeddings, and whether they could be compared at all. A pair of different sizes — the runtime changed under the same model name and the cache still holds the old vectors — has no score, and reporting it as zero made it a confident "unrelated".

func Tags added in v0.4.5

func Tags(r Record) string

Tags is the typed vocabulary of one ruling, rendered for a reader: the layer it lives at, how far it travels, how hard the evidence is, what to do about it. Exported because the handoff path needs it too — a ruling that crosses to another tool untagged is the one that gets re-run, and until it was rendered there this vocabulary only ever reached before_you_try.

func UnplacedToolchain added in v0.4.10

func UnplacedToolchain(failed []string) int

UnplacedToolchain counts the failed entries that name a tool, a package manager or PATH and carry no layer. "brain binary not on PATH" is about the machine that hit it, and in plain prose it reaches an agent on another toolchain reading like a fact about the code, which that agent then spends its reasoning placing. Guessing the layer here would be worse than leaving it out: a wrong "environment" hides a dead end that does apply. So the count only lets the checkpoint receipt ask the writer, who knows. An entry the writer already placed, at any layer, is not second-guessed.

Types

type Action

type Action string

Action is what the person who found this out recommends doing with that knowledge, distinct from Alternative, which is what to do instead.

const (
	ActionRetry        Action = "retry"         // worth trying again — conditions may have changed
	ActionChangeMethod Action = "change-method" // the goal is fine, this route to it is not
	ActionNarrowScope  Action = "narrow-scope"  // works, but only for a smaller case than proposed
	ActionAbandon      Action = "abandon"       // the goal itself should be dropped
)

type Degree

type Degree string

Degree says how hard the evidence is. "Failed" covers everything from "the spec forbids it" to "it flaked once and nobody chased why", and those deserve different weight from a reader deciding whether to retry.

const (
	DegreeContradicted Degree = "contradicted" // demonstrated not to work
	DegreePartial      Degree = "partial"      // worked for part of the case, not all of it
	DegreeInconclusive Degree = "inconclusive" // tried, no clear result either way
	DegreeUnstable     Degree = "unstable"     // worked sometimes, not reliably
)

type Layer

type Layer string

Layer names where in the stack a dead end lives. It is the first question anyone re-attempting the approach actually needs answered: a ruling about a dependency's behaviour transfers across projects that share the dependency; one about this project's design does not transfer at all.

const (
	LayerImplementation Layer = "implementation"
	LayerDesign         Layer = "design"
	LayerEnvironment    Layer = "environment"
	LayerDependency     Layer = "dependency"
	LayerRequirement    Layer = "requirement"
	// LayerUnclassified is not a fifth peer alongside the five above — it is
	// what an ordinary, free-prose Failed entry gets, so every checkpoint
	// written before this schema existed keeps reading exactly as it did.
	LayerUnclassified Layer = "unclassified"
)

type Record

type Record struct {
	Route       string
	Observation string
	Layer       Layer
	Scope       Scope
	Degree      Degree
	Action      Action
	Alternative string
	Raw         string
	// Truncated is set when the source entry ran past recordCap and was cut
	// to fit. The cut is visible here rather than silent, per the invariant
	// that a lossy operation says so.
	Truncated bool
}

Record is one dead end, typed. Route and Observation are required for a record to count as typed at all; Layer/Scope/Degree/Action/Alternative are each optional and simply absent from Render when unset.

Raw is always populated — the on-disk form, exactly as recorded — because a hand-edited or partially-typed entry should still be readable rather than silently losing whatever did not parse.

func ParseRecord

func ParseRecord(raw string) Record

ParseRecord turns one Failed or working-note entry into a typed Record.

The wire format is unchanged — Failed is still `[]string`, and a record is still one bullet, one line — so the shape lives entirely in the text: a line of `key: value` segments joined by " | ", anchored on a `route:` key. Anything else, including every Failed entry ever written before this existed, is not that shape and comes back as free prose with Layer: unclassified — the same record type, so a caller never has to branch on which kind of entry it got.

func (Record) Typed

func (r Record) Typed() bool

Typed reports whether this parsed as the structured shape at all, rather than falling back to free prose. Used to decide whether Layer/Scope/etc are worth rendering.

type Ruling

type Ruling struct {
	Text    string  `json:"text"`
	Project string  `json:"project"`
	Agent   string  `json:"agent"`
	When    int64   `json:"when"`
	Slug    string  `json:"slug,omitempty"`
	Source  Source  `json:"source"`
	Score   float64 `json:"score"`
	// Elsewhere is true when this was ruled out on a different project than the
	// one being asked about. Still worth saying, and worth flagging: what failed
	// on other hardware may not fail here, and presenting it as settled would be
	// the same overreach this package exists to prevent.
	Elsewhere bool `json:"elsewhere"`
	// Record is Text, typed — Layer: unclassified and a Route equal to Text
	// itself for every ruling recorded before this schema existed. Kept
	// alongside Text rather than replacing it so nothing here has to branch on
	// which kind of entry it got.
	Record Record `json:"record"`
	// Stale marks a version-bound ruling old enough that the dependency it
	// names may have moved since — see PossiblySuperseded. Never grounds for
	// dropping the ruling, only for saying so.
	Stale bool `json:"stale,omitempty"`
	// Commit is the repository's HEAD when the checkpoint holding this was
	// written. Empty for a working note, which records no repository.
	Commit string `json:"commit,omitempty"`
	// Drift is set when the files this ruling names have changed since
	// Commit — see MarkDrift. Like Stale, a reason to say so, never to drop it.
	Drift *gitstate.Drift `json:"drift,omitempty"`
	// Method is which arm of Matches caught this: the same words, the same
	// idea, or both. A match is a claim, and how it was made is part of how
	// far to trust it.
	Method string `json:"method,omitempty"`
}

A Ruling is something already tried that did not work.

func AtPath added in v0.5.2

func AtPath(db *sql.DB, vaultDir, project, rel string) ([]Ruling, error)

AtPath returns the checkpoint rulings in project, and in its worktree scopes, that name the repository file rel, newest first.

rel is matched as written ("internal/parse/reader.go") and by its base name for a ruling that only said "reader.go". A ruling that spelled out a different directory names a different file and is not returned.

Working notes are not consulted: a note ruled out with `tried --ruled-out` reaches before_you_try and resume, and reading them here would put the notes table on every edit for the rarest kind of ruling.

func Check

func Check(vaultDir string, db *sql.DB, p *provider.Provider, embedModel, proposed, project string, k int) ([]Ruling, error)

Check returns the recorded dead ends bearing on a proposed approach, best match first.

Two ways to match, each needing a little of the other near its floor (see cosineAlone). Lexical containment catches a proposal restating the original almost word for word, which is the common case when an agent has read the project note and is working from the same vocabulary. Embedding similarity catches the opposite case — the same idea in different words — and is what makes this useful across projects, where the vocabulary has no reason to line up. With no embedder available the lexical arm still works, so this degrades rather than disappearing.

func CheckNoting added in v0.4.4

func CheckNoting(vaultDir string, db *sql.DB, p *provider.Provider, embedModel, proposed, project string, k int) (hits []Ruling, semanticErr error, err error)

CheckNoting is Check, also returning why the semantic arm did not cover the whole corpus, if it did not. Degrading to lexical is fine; doing it silently made a weaker check read exactly like a full one that found nothing.

func Collect

func Collect(vaultDir string, db *sql.DB, project string) ([]Ruling, error)

Collect gathers every recorded dead end. An empty project means the whole vault, which is the interesting case: the approach that failed on one project is often the same approach about to be proposed on another.

type Scope

type Scope string

Scope says how far a ruling travels. The distinction Check already draws between "this project" and "elsewhere" is about where a ruling was made; Scope is about where it still applies, which is a different question — a version-bound ruling may no longer hold even on the project that made it.

const (
	ScopeLocal        Scope = "local"         // true of this codebase's particular choices, not the approach in general
	ScopeVersionBound Scope = "version-bound" // true against a dependency version that can move out from under it
	ScopeGeneral      Scope = "general"       // true of the approach itself, not tied to a version or a local choice
)

type Source

type Source string

Source says where a ruling came from, which is also how much to trust it.

const (
	// FromCheckpoint is a Failed entry someone deliberately recorded.
	FromCheckpoint Source = "checkpoint"
	// FromNote is a working note that reads as a failure. Never committed, so
	// it may be a first impression rather than a conclusion.
	FromNote Source = "working note"
)

Jump to

Keyboard shortcuts

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