Ramblings of an aging IT geek
← Ramblings of an aging IT geek
ai

Decisions, Weighted

A blueprint for working in existing codebases needed a way to outrank modern best practice, so aide decisions now carry a precedence weight.

Listen to this post
Brass scales weighing one heavy tag against a stack of thin paper cards

aide keeps a project's architectural decisions in the repo and injects them into every session, so agents are taught the rules rather than guessing at them. You can think of the decision store as the project's rule book: the conventions, constraints and background knowledge that need to be known and followed, written down once instead of re-explained every time somebody opens a session.

Blueprints are groupings of those decisions that align to some body of guidance or best practice, so you can onboard a new project in one command rather than typing out the same opinions about Go that you type out every time. Estates are the other half of it, decisions travelling between related projects, usually from a parent repo down into the subprojects it contains. All of it is about getting the right information in front of the model so it makes sensible choices and doesn't invent its own.

Until recently I never had any need for priorities between decisions. The latest revision for a given topic wins, and I think that is still the right model. What changed is that I added a blueprint for working in existing projects, honouring the conventions that are already there, and that immediately raised a problem: a language blueprint full of modern best practice will quite happily override the norms of a codebase that has been doing it another way for years. This release fixes that, along with a few other things I'd been meaning to get to.

Precedence, and One Threshold

Decisions now carry an integer precedence, where higher gets injected earlier. For most of them that is all it does, it's a sort key and nothing more.

aide decision set house-style "Follow the repo" --precedence=100

The exception is 100. At or above that threshold a decision comes out of the ordinary set altogether and gets rendered in a block of its own, ahead of everything else, with the relationship spelled out rather than left to inference:

## Overriding Decisions

These take precedence over every decision below and over general best practice.
Where they conflict with anything else, these win:

- **existing-codebase-precedence**: The repository is the authority…

So something at 200 outranks something at 100 inside that block, while something at 80 stays in the ordinary set, sorted above the defaults but not claiming anything over them.

Precedence Renders in Claims
>= 100 ## Overriding Decisions "where this conflicts with anything below, this wins"
199 ## Project Decisions, above the defaults nothing
0 (default) ## Project Decisions nothing
negative ## Project Decisions, last nothing

The explicit header on that block is what makes the override clear, rather than leaving it to be inferred from where a decision happens to appear in the list.

The weight itself is plumbed through the proto field, the Bolt store, the gRPC server, the CLI, share export and import, blueprints, the web API and its badge, and both session-start and subagent injection, so a decision that overrides in your session overrides in a subagent's too.

flowchart TD
    L[local store] --> S{precedence}
    P[parent estate] --> S
    R[peer rings] --> S
    S -->|100 and above| O["## Overriding Decisions"]
    S -->|below 100| D["## Project Decisions"]

One useful property of the design is that an omitted precedence inherits whatever the current revision carries:

func ResolvePrecedence(g DecisionGetter, topic string, requested *int) int {
	if requested != nil {
		return *requested
	}
	if prev, err := g.GetDecision(topic); err == nil && prev != nil {
		return prev.Precedence
	}
	return memory.PrecedenceDefault
}

Decision writes are revisions rather than mutations, so without that, rewording a guardrail from the CLI would write a new revision at the default weight and silently demote a 100 down to a 0. An explicit value still wins, including an explicit 0, so you can demote something deliberately if you want to. All three write paths go through the same resolver, which is the only reason I trust it.

I also fixed an existing bug I hadn't found until now. sessionFetchContext was ranging a map:

// Ranging a map made this block reorder on every session, which is
// noise in agent context and defeats prompt caching on the prefix.

Go randomises map iteration, so the injected block was coming out in a different order every session despite the content being identical, which is noise in the context and leaves the prompt cache without a stable prefix to reuse. It's now topic-sorted within each precedence band with the ring order preserved inside a weight, so local decisions still come before inherited ones, and three consecutive runs come out byte-identical.

The Blueprint That Started It

existing-software-project is the blueprint that prompted all of the above, and the only one that ships above the threshold. It's a bundle of decisions about working in a codebase you didn't write, all at default_precedence: 100, and unlike the language blueprints it includes nothing of its own, since it's meant to sit alongside whatever language blueprints you already have:

aide blueprint import existing-software-project

The existing-* decisions are there to honour a project's existing style, rules and process: read the surrounding code before editing it, use the committed toolchain, treat linter config as binding, keep the diff to what the task needs. An explicit instruction in the current task still outranks all of it and general best practice comes last, so the bundle won't impose something like Conventional Commits on a repository whose history has never used them.

And Some Python

I've been working on more Python projects recently, so those decisions are now a blueprint too: python, with python-django, python-api and python-github-actions on top. Detection also picks up requirements.txt and uv.lock for Python, and manage.py for Django.

The blueprints documentation has the per-decision breakdowns for each of the new bundles if you want the detail.