furrow

package
v0.4.2-rc.2 Latest Latest
Warning

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

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

Documentation

Overview

Package furrow is codeaf's seam onto `furrow`, a separate program codeaf carries inside itself (Agent-Field, Apache-2.0, https://github.com/Agent-Field/furrow).

furrow copy-on-write forks a whole workspace — files, dependencies, `.env`, the dev database, git's own mutable state — into byte-exact universes in about a second, seals that workspace continuously into an immutable content-addressed timeline, and syncs it between machines encrypted below the transport. It is Rust and codeaf is Go, so THE ONLY INTEGRATION IS THE CLI — which is fine, because the CLI is furrow's declared API: `--json` everywhere, stable IDs, and destructive operations gated on an explicit ID plus `--yes`.

EVERY codeaf IS A codeaf WITH FURROW. The binary rides inside this one and is written out on first need (internal/furrowbin), so the half of the answer that used to vary by machine — is furrow installed — no longer does. What still varies is the half a person controls per project: a folder nobody ran `furrow watch` in is a folder furrow will not act on.

THE LAW THIS WHOLE PACKAGE IS WRITTEN TO IS THE CODEBASE'S OWN: A CAPABILITY THAT CANNOT WORK IS ABSENT, NOT BROKEN. A folder furrow was never pointed at — or the rare machine where the binary could not be written out at all — and Tools returns nothing at all: the model is never handed a verb whose every call would be a refusal, and no other part of codeaf notices this package exists. Everything here hangs off Detect, which is why Detect is the first thing in the file and the most carefully cached: it is asked far more often than anything else is done.

It is also written to be wrong about furrow safely. furrow's JSON is furrow's to change, and a version this package has never seen must degrade to "I could not read that" rather than to a panic or, worse, to a confident misreading of a restore. So every decode takes the few fields it needs and ignores the rest, every optional field is treated as optional, and no exit code is trusted over a document that parsed (see [run]).

Index

Constants

View Source
const (
	Binary       = "furrow"
	BinaryEnvVar = "CODEAF_FURROW"
)

Binary is the program this package shells out to. It is normally the copy codeaf carries and writes out itself; PATH is the fall-back, and CODEAF_FURROW overrides both for somebody who means a particular binary — their own build, or a newer furrow than this codeaf is pinned to. [lookBinary] has the order and the reason for it.

View Source
const (
	// ToolSnapshots is the read-only look at the timeline.
	ToolSnapshots = "workspace_snapshots"
	// ToolRestore puts the folder back, and is the only destructive one.
	ToolRestore = "workspace_restore"
	// ToolFork runs something risky in a copy of the whole workspace.
	ToolFork = "workspace_fork"
	// ToolMerge lands a copy's changes, gated on a check.
	ToolMerge = "workspace_merge"
)

The four verbs furrow puts on the belt, and the one line that decides whether any of them are there.

THEY ARE CONDITIONAL ON SOMETHING NO CONFIGURATION CAN FIX: whether this folder has been attached with `furrow watch`. The binary itself is no longer in question — codeaf carries furrow inside it (internal/furrowbin) — so the half that used to vary by machine does not, and the half that remains is the one a person decides per project. A belt is a promise, though: every tool on it is something the model has been told it can do, and a `workspace_restore` that answers "this repository is not watched" costs the model a call, reads as temporary, and stays in its plan for the rest of the turn. A model that was never told about these simply says the files cannot be put back, which is true and free.

The names all begin `workspace_` for a second reason, and it is not tidiness. codeaf already has a rewind, and it is an edit of the CONVERSATION that deliberately touches nothing on disk (internal/session/rewind.go). These move the folder. A model holding both must never confuse them, so the two families do not share a word: one is rewind, the other is workspace_restore.

Variables

View Source
var ErrNotHere = errors.New("furrow is not on this machine")

ErrNotHere is what Attach answers on a machine with no furrow to run at all. It is the one refusal that costs nothing and says nothing about the folder, so it is the one a caller may want to tell apart from a furrow that ran and said no.

View Source
var ErrUnreadable = errors.New("furrow: unreadable answer")

ErrUnreadable says furrow answered in a shape this package could not decode. It is a sentinel because it is the one failure a caller may want to word differently from every other: the rest mean furrow said no, this one means codeaf and furrow disagree about what furrow says, and only the second is worth quoting a version at somebody over.

Functions

func Descriptions

func Descriptions() map[string]string

Descriptions and Schemas are the tool metadata as data, for a caller that registers these somewhere other than a bare.Tool slice — a remote belt summary, a manual gate, a test that wants the exact strings. They are the same values the tools carry, read from one place so the two cannot drift.

func Forget

func Forget()

Forget drops the cached answer for every workspace. It exists for tests, which change what is on PATH between cases and would otherwise read a neighbour's answer, and for a caller that has just watched the person attach a folder and wants the next question answered by furrow rather than by a thirty-second-old memory.

func Names

func Names() []string

Names is every tool this package can put on a belt, for the manual gate and for a caller that wants to name them without building them.

func Program

func Program() string

Program names the furrow this package would run — its path, size and modification time — or "" when there is none. It runs nothing, so asking is one stat.

IT EXISTS FOR A CALLER THAT REMEMBERS WHAT FURROW DID. A furrow that could not make something for a folder yesterday may be able to today, and the thing that changed is nearly always the program: a codeaf carrying a newer pin writes a newer binary under a newer name (furrowbin), and a person who points BinaryEnvVar at their own build has changed it too. A memory keyed on this string forgets itself the moment either happens.

func Schemas

func Schemas() map[string]string

func Tools

func Tools(ctx context.Context, root string) []bare.Tool

Tools is the whole seam a caller wires in one line: the four verbs when furrow is here and this folder is attached to it, and NOTHING AT ALL otherwise. A caller writes

tools = append(tools, furrow.Tools(ctx, workspace)...)

and has nothing else to remember. The nil case is not an error and is not worth logging: on a machine without furrow it is what every session sees, and it is the design working.

Types

type Fork

type Fork struct {
	// Name is the fork's stable name, and the only handle [Workspace.Merge]
	// takes. Path is where it was materialized.
	Name string
	Path string

	// Base and Head are the snapshot the universe started from and the one
	// furrow sealed of it when the command finished.
	Base string
	Head string

	// ExitCode is the command's own status. A non-zero one is not a furrow
	// failure and is never reported as one: the fork was made, the command ran,
	// and it said no.
	ExitCode int

	// Output is what the command printed, captured and capped. Under --json
	// furrow sends a universe's own stdout to stderr so the JSON stays clean,
	// which is why both streams end up here as one thing to read.
	Output string
}

Fork is what running a command in its own universe came back with.

type Merge

type Merge struct {
	Fork string

	// Landed reports that the workspace really changed. It is false for a
	// preview, false when the check failed, and false when there were
	// conflicts — three different reasons for the same fact, which is why the
	// reason travels beside it rather than being inferred from it.
	Landed bool

	// Result is the snapshot the merge sealed, set only when Landed.
	Result string

	// Changes is how many paths the merge covers, and Conflicts is every path
	// it stopped on. A merge with conflicts changes nothing.
	Changes   int
	Conflicts []MergeConflict

	// CheckOutput is what the verification command printed, whether it passed
	// or failed. It is capped like every other captured stream.
	CheckOutput string

	// CheckFailed reports that a check was given and said no. NOTHING WAS
	// MERGED when it is true, which is the entire point of giving a check.
	CheckFailed bool
}

Merge is what landing a universe came back with.

type MergeConflict

type MergeConflict struct {
	Path string
	Kind string
}

MergeConflict is one path two universes disagree about.

type Presence

type Presence struct {
	// Installed reports that a furrow binary was found, and Path is where. On
	// an ordinary build this is true everywhere — the binary is carried — and
	// the field is kept because the two halves are still separately
	// interesting: a state root that could not be written to is a machine
	// where it is false, and that is a fault worth being able to name.
	Installed bool
	Path      string

	// Version is what `furrow --version` printed, trimmed — "furrow 0.1.0".
	// It is carried for a person to read and for a bug report to quote, and
	// nothing in this package branches on it. Version-sniffing a tool whose
	// contract is its JSON would be a second contract to keep in step with the
	// first, and the decoders here are already written to survive a shape they
	// do not recognise.
	Version string

	// Attached reports that THIS folder is one furrow is watching, which is the
	// half a person controls per project rather than per machine.
	Attached bool

	// Head is the newest sealed snapshot's id, and Watching reports whether the
	// background watcher is running. A workspace can be attached with its
	// watcher stopped — the timeline is then frozen at Head until somebody runs
	// `furrow watch` again — and a caller that offers a restore point without
	// saying so is offering a stale one.
	Head     string
	Watching bool

	// Reason is furrow's own first line when Installed is true and Attached is
	// false. It is furrow's wording and not ours on purpose: the person is
	// going to type furrow's commands to fix it, so they should be reading
	// furrow's account of the problem.
	Reason string
}

Presence is the whole of what this package knows about furrow and one folder, and it is deliberately more than a boolean: a caller offering the person something wants to say WHICH of the two halves is missing, because "install furrow" and "run furrow watch here" are completely different sentences.

func Detect

func Detect(ctx context.Context, root string) Presence

Detect answers "is furrow here, and is this folder attached to it?" — the question everything else in this package hangs off.

It is at most two short execs: `furrow --version`, which touches nothing, and `furrow --repo <root> --json status`, which opens the store. Both are cached together for [detectTTL] against the absolute root, because the two halves are useless apart and a caller asking one has always just asked the other.

It never returns an error. Every way this can fail — no binary, a binary that will not run, a folder furrow was never pointed at, a store that will not open — is the same answer to the caller: the seam is not available here, and what it does about that does not depend on which. The distinctions a person CAN act on are carried in the fields instead.

func (Presence) Available

func (p Presence) Available() bool

Available is the one condition every caller in this codebase should branch on. Both halves have to be true for a single furrow verb to work, so they are asked as one question rather than left to each caller to remember to and.

type Restore

type Restore struct {
	// Snapshot is the restore point aimed at, and Changes is every path the
	// restore covers — empty when the workspace already matches it.
	Snapshot string
	Changes  []RestoreChange

	// Applied distinguishes a preview from a restore that really happened.
	Applied bool

	// Undo is the snapshot furrow sealed of the CURRENT state immediately
	// before applying, set only when Applied. It is the reason a restore is not
	// a one-way door — furrow seals before it restores, so a restore is itself
	// rewindable — and a caller that does not offer it back to the person is
	// hiding the safest thing about the operation.
	Undo string
}

Restore is what a restore preview or a restore says about itself.

type RestoreChange

type RestoreChange struct {
	Path   string
	Action string
}

RestoreChange is one path a restore would touch, or did.

type Snapshot

type Snapshot struct {
	// ID is furrow's own snapshot id, and the only thing a restore will accept.
	ID string

	// SealedAt is when furrow sealed it. This is THE ALIGNMENT KEY between the
	// two kinds of rewind codeaf has: the conversation's cut points live in
	// internal/session and know nothing about the workspace, so pairing them is
	// done on the clock and on nothing else (see [Workspace.PointNear]).
	SealedAt time.Time

	// Label is what the seal was called, when it was called anything. A seal
	// furrow made on its own has none; a seal made through [Workspace.Mark]
	// carries the turn it belonged to.
	Label string

	// Trigger is furrow's word for why the seal happened, and Grade is furrow's
	// declaration of how exactly this snapshot can be materialized again —
	// fidelity is declared and never implied, which is furrow's own rule and
	// worth carrying rather than flattening.
	Trigger string
	Grade   string

	// Pinned reports that the person has held this snapshot exact against
	// timeline thinning, so it will still be there later.
	Pinned bool
}

Snapshot is one sealed restore point on the workspace timeline: a moment the whole folder can be put back to, byte for byte.

func (Snapshot) Short

func (s Snapshot) Short() string

Short is the snapshot id the way furrow's own timeline prints it — the first twelve characters. It is for a person to read and never for a call to pass; furrow resolves prefixes, but this package hands back the whole id so that nothing it returns can resolve to the wrong snapshot later.

type SyncOffer

type SyncOffer struct {
	// Workspace is the folder being offered, and Name is the shared workspace
	// name suggested for both ends — furrow's own default, the folder's name.
	Workspace string
	Name      string

	// Here is what to run on the machine holding the folder, and There is what
	// to run on the machine that should receive it. There carries a
	// placeholder for the recovery key, because this package does not have one
	// and will not go looking.
	Here  []string
	There []string

	// Note is the honest edge, in furrow's own terms and codeaf's law's terms
	// at once. It is not decoration: somebody who reads only this field should
	// still not be surprised later.
	Note string
}

SyncOffer is the pairing codeaf OFFERS rather than performs: the two commands that put one folder on two machines, for the person to run themselves.

codeaf NEVER RUNS `furrow remote add` ITSELF, and the reason is one line of furrow's output. Pairing prints the workspace's recovery key — the only thing that can read this workspace's ciphertext anywhere — and a secret that passes through a tool result has been written into a transcript, a journal, and possibly a screen somebody else is looking at. So the offer is commands, the person runs them, and the key never enters codeaf at all.

type Workspace

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

Workspace is one attached folder, and the receiver every operation in this package hangs off. Holding it is a claim that furrow was here and this folder was attached at the moment Open asked, which is the strongest claim anything can make about another process.

func Attach

func Attach(ctx context.Context, root string) (*Workspace, error)

Attach is Open for a caller that is willing to ATTACH THE FOLDER ITSELF.

THE RULING BEHIND IT: every codeaf carries furrow, so a capability that only engages when somebody remembered to type `furrow watch` is a capability the binary has and never uses — which is this codebase's absent-not-broken law running in the bad direction. A folder codeaf is about to write in is a folder codeaf may attach, on the same consent as the write; nothing here reaches a folder that was not already going to be worked in.

It attaches WITHOUT LEAVING A WATCHER RUNNING (`--no-daemon`). What the caller needs is the ability to fork the live workspace, which does not depend on a background sealer, and a program that quietly started a daemon in somebody's project would be doing more than the write it was consenting to.

Every failure answers nil, exactly as Open does, and the caller falls to whatever it would have done on a machine without furrow. THE NIL NOW COMES WITH ITS REASON, in furrow's own words where furrow gave any: a caller that falls to its rung below and cannot say why is a caller whose every task paid for an attach nobody could debug — ErrNotHere for the machine with nothing to run, and furrow's first line for everything else.

func Open

func Open(ctx context.Context, root string) *Workspace

Open is the seam: the workspace when furrow can act on this folder, and nil when it cannot. THE NIL IS THE POINT — a caller writes `if ws := furrow.Open(…); ws != nil` and every capability behind it is absent rather than present and refusing, which is this codebase's law about tools stated in a return type.

func (*Workspace) ApplyRestore

func (w *Workspace) ApplyRestore(ctx context.Context, snapshot string, paths []string) (Restore, error)

ApplyRestore puts the workspace back, whole or by path.

It passes furrow's `--yes`, which is not this package waving a gate through: furrow's gate is an explicit ID plus a confirmation, and the confirmation codeaf is passing on is a person's, collected before this is ever called. The tool in tools.go is where that is enforced, and it is enforced by requiring a separate argument rather than by trusting a model to have meant it.

func (*Workspace) DropFork

func (w *Workspace) DropFork(ctx context.Context, name, destination string) error

DropFork retires a task's files and timeline together. Furrow's keep-files option retains the timeline too, so callers must retire before deleting files. The expected destination prevents a stale checkpoint from deleting another fork.

func (*Workspace) Fork

func (w *Workspace) Fork(ctx context.Context, name, destination string) (Fork, error)

Fork materializes a copy-on-write universe of the whole workspace and HANDS IT BACK, running nothing inside it.

It is the door Workspace.RunInFork is not. RunInFork exists for the model's own `workspace_fork` verb, where a universe is the safe place one command gets to run; this exists for the harness, where a universe is the GROUND a task is given and everything that happens in it happens afterwards, over hours, through the task's own belt. Until this door existed the rest of codeaf had to ground a task with `git worktree add`, which carries HEAD and leaves the dirty tree, the untracked files and the `.env` behind — the defect the ground law (internal/session/taskground.go) was written from.

destination is where the universe is put. An empty one lets furrow choose `<repo>.furrow-forks/<name>` beside the workspace, which is furrow's own default and the wrong answer for a task — a task's world belongs under the session that asked for it, not beside the person's project — so every caller in this codebase names one.

func (*Workspace) Forks

func (w *Workspace) Forks(ctx context.Context) ([]Fork, error)

Forks lists the universes that exist, with what each has changed.

func (*Workspace) Mark

func (w *Workspace) Mark(ctx context.Context, agent, turn string) (string, error)

Mark seals the workspace now and attributes the seal to a turn, so that a conversation rewound later has a workspace state to be offered beside it.

It is `furrow hook turn-end`, which is the same door `furrow hook install` wires a generic harness into — called directly instead, because codeaf knows its own turn boundaries and does not need a shell adapter to tell it about them. The label furrow writes is its own: `hook turn-end agent=<agent> turn=<turn>`.

It is only ever called on a Workspace, and that matters: the command furrow runs underneath ATTACHES a folder it was not already watching, and attaching somebody's folder to a program because a turn ended is not a thing codeaf may decide. Open having already said yes is what makes this safe.

func (*Workspace) MergeFork

func (w *Workspace) MergeFork(ctx context.Context, fork, check string, preview bool) (Merge, error)

MergeFork lands a universe's changes back into the workspace, verifying them first when a check is given: furrow materializes the merged result in a scratch workspace, runs the check there through `/bin/sh -c`, and lands nothing unless it passes.

preview plans the merge and reports its changes and conflicts without materializing or checking anything.

func (*Workspace) OfferSync

func (w *Workspace) OfferSync(remote string) SyncOffer

OfferSync builds the pairing offer for a remote. The remote is furrow's own spelling: an ssh:// host reachable over a LAN or a tailnet, an s3:// bucket used as an always-available encrypted mailbox, or a path to a directory.

func (*Workspace) PointNear

func (w *Workspace) PointNear(ctx context.Context, when time.Time) (Snapshot, bool, error)

PointNear is the aligned-rewind seam: the newest restore point sealed at or before a moment, which is the workspace state a conversation cut at that moment was looking at.

It takes a time and not a rewind point because THE TWO REWINDS DO NOT SHARE A VOCABULARY AND SHOULD NOT. internal/session's RewindPoint is an index into a transcript; furrow's snapshot is a content hash of a folder; the only thing both ends genuinely agree on is the clock. Keeping the join here, on one argument, means the conversation's rewind stays exactly what its own package says it is — an edit of the conversation, never of the workspace — and this package is the only place that ever offers to move the second one too.

Reported false when the timeline reaches no further back than the moment asked about, which is the ordinary answer for a folder attached to furrow only recently: there is no restore point from before furrow was watching, and saying so is better than offering the oldest one there is.

func (*Workspace) PreviewRestore

func (w *Workspace) PreviewRestore(ctx context.Context, snapshot string, paths []string) (Restore, error)

PreviewRestore reports what restoring to a snapshot would change, and touches nothing. paths, when given, narrow the restore to those repository-relative paths — the `.env` back and newer work untouched.

func (*Workspace) Root

func (w *Workspace) Root() string

Root is the absolute path of the workspace, canonical as Open resolved it.

func (*Workspace) RunInFork

func (w *Workspace) RunInFork(ctx context.Context, name, command string) (Fork, error)

RunInFork materializes a copy-on-write universe of the whole workspace — files, dependencies, `.env`, the dev database, git's own mutable state — and runs one command inside it, leaving the real workspace untouched.

The command goes to `/bin/sh -c`, which is the same shell furrow itself runs a merge check through, so a command that works in one works in the other.

No timeout is imposed. The command's own runtime is the person's business and the caller's context is what carries their decision to stop; a ceiling invented here would be a second, quieter limit beside whichever one the caller already applies.

func (*Workspace) Snapshots

func (w *Workspace) Snapshots(ctx context.Context, limit int) ([]Snapshot, error)

Snapshots is the workspace timeline, newest first, as furrow ordered it.

func (*Workspace) Tools

func (w *Workspace) Tools() []bare.Tool

Tools is the same four verbs for a caller that already holds the workspace — a host that opened it once and hands it to every session it serves.

Jump to

Keyboard shortcuts

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