note

package
v0.10.70 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package note is the note-passing medium for multi-agent driftlessaf runs.

Different agents don't share memory, so they pass work to each other by persisting it: a step Puts a note body, and a downstream step Gets it back by the same coordinates. A note's identity is four coordinates — (Key, Run, Name, Author) — and its content-addressed primary key is Ref, a SHA-256 any agent can compute from the coordinates alone, so a note is addressable without a lookup or an opaque handle.

The store

Store is the persistent read/write medium and the durable audit copy at once. This package ships the in-memory backend (NewMem) alongside the contract — it pulls in no cloud SDK, so it stays here; only a durable backend that needs a cloud SDK (GCS) lives in a sibling sub-package. Following "accept interfaces, return structs", a backend returns a concrete type and a consumer accepts Store — or a narrower interface of just the methods it uses.

Listing

Store.List filters by any subset of coordinates and returns a bounded Page of coordinates; a caller pages by passing Page.Cursor back in Filter.Cursor until it is empty. Bodies are streamed on Store.Get, so a fan-in lists coordinates and reads only the bodies it needs.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNotExist = errors.New("note: not found")

ErrNotExist is returned by Get when no note exists for the given coordinates.

Functions

func Ref

func Ref(key string, run int, name, author string) string

Ref is the deterministic primary key for a note: the hex SHA-256 of its four coordinates in canonical (Key, Run, Name, Author) order. Any agent can compute it from the coordinates alone, so a note is addressable without a lookup or an opaque handle. There is deliberately no Ref field on Note — the key is always derived, never stored, so it can't disagree with the coordinates.

Each string coordinate is length-prefixed (an 8-byte big-endian length, then the bytes) rather than joined by a separator. Length-prefixing is injective for ALL inputs: field boundaries come from the lengths, not the content, so no coordinate value — even one containing the byte a separator would use — can be shifted across a boundary to forge another note's Ref. A bare separator (e.g. NUL) only disambiguates coordinates that never contain that byte; a coordinate free to hold it can collide, e.g. ("a","\x00b") vs ("a\x00","b").

Example

ExampleRef shows the content-addressed key: any agent computes the same Ref from a note's coordinates, so it can address another agent's note without a lookup — and changing any coordinate yields a different key.

package main

import (
	"fmt"

	"chainguard.dev/driftlessaf/agents/note"
)

func main() {
	ref := note.Ref("harden:widget", 1, "fix_plan", "claude")
	same := note.Ref("harden:widget", 1, "fix_plan", "claude")
	otherAuthor := note.Ref("harden:widget", 1, "fix_plan", "gemini")

	fmt.Println("stable:", ref == same)
	fmt.Println("per-author:", ref != otherAuthor)
}
Output:
stable: true
per-author: true

Types

type Filter

type Filter struct {
	Key    string  // "" matches any key
	Run    *int    // nil matches any run
	Name   string  // "" matches any name
	Author *string // nil matches any author; &"" selects the shared/synthesizer scope exactly
	Limit  int     // 0 = the store's default cap; List never returns an unbounded slice
	Cursor string  // opaque continuation from a prior Page for the SAME filter; "" = the first page
}

Filter selects notes by any subset of coordinates; empty/nil fields are wildcards. Limit and Cursor bound and page a List.

type Mem

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

Mem is an in-memory Store for tests and single-process use. It is safe for concurrent use. The zero value is not usable; call NewMem.

Example

ExampleMem passes notes through the in-memory store: two authors each Put their fix_plan for one key+run, a consumer Gets one back by its coordinates, and List gathers every author's fix_plan for that run.

package main

import (
	"context"
	"fmt"
	"io"
	"strings"

	"chainguard.dev/driftlessaf/agents/note"
)

func main() {
	ctx := context.Background()
	store := note.NewMem()

	run := 1
	_ = store.Put(ctx, note.Note{Key: "harden:widget", Run: run, Name: "fix_plan", Author: "claude"}, strings.NewReader("plan A"))
	_ = store.Put(ctx, note.Note{Key: "harden:widget", Run: run, Name: "fix_plan", Author: "gemini"}, strings.NewReader("plan B"))

	_, rc, err := store.Get(ctx, "harden:widget", run, "fix_plan", "claude")
	if err != nil {
		panic(err)
	}
	defer rc.Close()
	body, err := io.ReadAll(rc)
	if err != nil {
		panic(err)
	}
	fmt.Printf("claude fix_plan: %s\n", body)

	// Gather every author's fix_plan for this key+run — the adversarial-review fan-in.
	page, err := store.List(ctx, note.Filter{Key: "harden:widget", Run: &run, Name: "fix_plan"})
	if err != nil {
		panic(err)
	}
	fmt.Println("fix_plans in run:", len(page.Notes))

}
Output:
claude fix_plan: plan A
fix_plans in run: 2

func NewMem

func NewMem() *Mem

NewMem returns an empty in-memory note store.

func (*Mem) Get

func (m *Mem) Get(_ context.Context, key string, run int, name, author string) (Note, io.ReadCloser, error)

Get returns the note's coordinates and a reader over a copy of its body, or ErrNotExist when absent. The reader needs no cleanup, but is a ReadCloser to satisfy Store so callers Close it uniformly across backends.

func (*Mem) List

func (m *Mem) List(_ context.Context, f Filter) (Page, error)

List returns a bounded, Ref-ordered Page of the coordinates matching f. It caps at Filter.Limit (or defaultListLimit) and sets Page.Cursor when more matches remain; pass that cursor back in Filter.Cursor for the next page.

func (*Mem) Put

func (m *Mem) Put(_ context.Context, n Note, body io.Reader) error

Put validates n, reads body to completion, and stores it under n's Ref, overwriting any existing note with the same coordinates. The body is buffered, so a caller mutating its source after the write can't alter stored bytes.

type Note

type Note struct {
	Key    string // workqueue key — the item the run is about (e.g. "harden:<skill>")
	Run    int    // run number — the pipeline attempt for this key, 1-based
	Name   string // step/note name: "fix_plan", "critique", …
	Author string // who produced it: a model, a synthesizer, or a human ("" for shared notes)
}

Note is the identity of one note passed between agents: the four coordinates that address it. Because agents don't share memory, a note is passed by being persisted — a step Puts its body under these coordinates and a downstream step Gets it back by the same ones. The body is streamed separately (see Store), not carried on the struct, so a note of any size moves without buffering it in full.

func (Note) Validate

func (n Note) Validate() error

Validate reports whether n has the minimum coordinates a store will accept. It is the single gate every backend's Put calls, so "what is a storable note" is defined once rather than drifting per backend. Author is optional ("" is the shared/synthesizer scope); the body may be empty.

type Page

type Page struct {
	Notes  []Note
	Cursor string // "" when the listing is exhausted
}

Page is one bounded slice of a List plus the cursor for the next.

type Store

type Store interface {
	// Put stores body under Ref(n.Key, n.Run, n.Name, n.Author), overwriting any
	// existing note with those coordinates. It reads body to completion (a nil
	// body is empty) and returns the error from n.Validate when n is not a
	// storable note.
	Put(ctx context.Context, n Note, body io.Reader) error

	// Get returns the note's coordinates and an open reader over its body, or
	// ErrNotExist when no note is stored under the coordinates. The caller must
	// Close the returned reader.
	Get(ctx context.Context, key string, run int, name, author string) (Note, io.ReadCloser, error)

	// List returns a bounded Page of the coordinates matching f, in a stable
	// order; bodies are fetched separately with Get. When Page.Cursor is non-empty
	// there are more matches: pass it back in Filter.Cursor for the next page. A
	// cursor is only valid replayed against the same Filter (its other fields
	// unchanged); pairing it with a different filter is undefined.
	List(ctx context.Context, f Filter) (Page, error)
}

Store is the note-passing medium: a persistent read/write store of note bodies keyed by (Key, Run, Name, Author). It is the passing medium and the durable copy at once. A backend returns a concrete type implementing Store (see NewMem); a consumer accepts Store, or a narrower interface of just the methods it uses.

Jump to

Keyboard shortcuts

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