Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .agents/skills/effort-graph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
name: effort-graph
description: Journal reasoning (decisions, findings, issues, constraints, risks) into a Flatbread Effort Graph and recall it with bounded reads. Use when starting or resuming a thread of work, recording a decision or finding, resolving an issue, checking what is blocking or still open on an effort, or when the user mentions effort graph, journaling, blocking decisions, or agent memory.
---

# Effort Graph — agent journaling and recall

The Effort Graph is persistent, queryable memory for long-horizon work, stored
as markdown records in the repo. Six primitives: **Effort** (the anchor thread
of work), **Issue**, **Finding**, **Decision**, **Constraint**, **Risk**.
Every record belongs to exactly one Effort. You write through 13 typed
mutations and read through 5 bounded queries — never by hand-editing record
frontmatter (bodies may be edited freely).

Read [glossary.md](./glossary.md) for the primitive and edge semantics before
inventing a new record kind or relation.

All commands run from the project root via the `flatbread` CLI (`pnpm exec flatbread`, `npm exec -- flatbread`, `yarn flatbread`, or `bunx flatbread`).
Commands print one JSON object to stdout;
errors print JSON to stderr and exit 1.

## First activation

Read [setup.md](./setup.md), make the reviewed config and gitignore edits, then
run `flatbread effort bootstrap` followed by `flatbread effort bootstrap --verify`. Bootstrap is report-only and never edits project files.

## Prerequisites

Your `flatbread.config.*` must include the preset:

```js
import {
defineConfig,
sourceFilesystem,
transformerMarkdown,
effortGraphContent,
} from 'flatbread';

export default defineConfig({
source: sourceFilesystem(),
transformer: transformerMarkdown(),
content: [...effortGraphContent()],
});
```

Records live under `<root>/{efforts,issues,findings,decisions,constraints,risks}/`.
The write journal is `<root>/.journal/`; read digests cache under
`.flatbread/effort-graph/read-cache/` (both gitignored).

## Writing (journaling)

One command for all 13 mutations — pass the payload as a single JSON argument:

```bash
flatbread effort write '{"type":"WriteDecision","effort":"<eff-id>","title":"...","body":"...","derives_from":["<id>"]}'
```

Response: `{"generation":"<token>","artifacts":[{"id","path","operation"}],"touched":[...]}`.
**Capture `artifacts[0].id`** to wire later edges, and **keep `generation`**
for strict read-your-writes.

Full payload shapes for all 13 mutations: read [reference.md](./reference.md).
Critical semantics:

- Creates always start in the initial lifecycle state: `WriteDecision` →
`proposed`, `WriteIssue` → `open`, `WriteRisk` → `open`. You cannot pass a
state; use lifecycle mutations (`AcceptDecision`, `ResolveIssue`,
`MitigateRisk`, `SetRiskState`) to transition.
- `AcceptDecision` defaults `rejectSiblings: true`, which rejects ALL other
proposed Decisions in the same Effort. Pass `"rejectSiblings": false`
unless you deliberately want the competing proposals closed.
- Edges are forward-only in payloads (`derives_from`, `supersedes`,
`invalidates`); back-edges are materialized automatically.
- When superseding, open the new record's body with a short rollup of what
changed and why — reads render ancestors only as one-line checkpoints.
- For a hard-to-reverse, surprising decision made after a real trade-off, use
the Decision body as the durable rationale: include context, alternatives,
consequences, and reversal criteria. Do not create a parallel ADR; use
[effort-modeling](../effort-modeling/SKILL.md) when the decision is still
being grilled.

## Reading (recall)

Every read returns a bounded envelope, not records: a ≤160-token `summary`,
an `artifact_path` to a rendered markdown digest (the evidence — spend one
Read on it, or grep it), `served_generation`, page info, and ≤10 executable
`hints`. Digests cap at 25 records / one-hop expansion / 50 edges / 64 KiB.

Browse digests (`list`, `records`, `relations`, `blocking-decisions`) excerpt
each body at 600 chars / 12 lines (`[…truncated]`). **`effort get` digests
always include the full record body** (still subject to the 64 KiB digest
byte cap). Zoom in with `get`, then Read/grep that digest — do not open
`.flatbread-efforts/**/*.md` for normal full-body recall.

```bash
# What's gating this effort? (proposed Decisions deriving from open blocker Issues)
flatbread effort blocking-decisions <effortId>

# Resume: discover active Efforts first
flatbread effort list --status active

# Scoped listing with filters (AND across flags, OR within comma lists).
# --status filters Issues and --state filters Decisions, so combining them in
# one call ANDs across kinds and matches nothing — query each kind separately.
flatbread effort records <effortId> --kinds issue --status open --since 2026-07-01T00:00:00Z --limit 10
flatbread effort records <effortId> --kinds decision --state proposed --limit 10

# One-hop neighbors of a record
flatbread effort relations <effortId> <fromId> --relations derives_from,superseded_by

# Single record with full body; --resolve head follows supersession to the tip
flatbread effort get <id> [--resolve head]
```

Flags shared by reads: `--strict-min-generation <token>` (with optional
`--timeout-ms <ms>`, default 3000) and, on `list`/`records`/`relations`, `--limit`
(≤25) and `--cursor` (opaque `next_cursor` from a prior page; only valid for
the same query at the same generation).

`effort list` is bounded Effort discovery. It defaults to `active`; valid
statuses are exactly `active`, `paused`, `completed`, and `abandoned`.
Comma-separated statuses are ORed. Results use the shared `created_at`, then
`id` ordering. After discovery, use bounded effort-scoped reads.

**Consistency:** reads are eventual by default. Immediately after a write,
pass the returned generation as `--strict-min-generation` — you get either
fresh data or an `EFFORT_GRAPH_GENERATION_WAIT_TIMEOUT` error (exit 1),
never silently stale results. Do not build polling loops; the wait is
server-side.

## Recommended session workflow

1. **Resume / status briefing (bounded fast-path):** `effort list --status active`
and trust the returned digest. For each active Effort, run
`effort records <effortId> --kinds issue,decision` and read each record's
status/state from that one digest. Run `effort blocking-decisions <effortId>`
only for an Effort whose digest shows an open `blocker` Issue — skip it
otherwise. Do not open raw `.flatbread-efforts/**/*.md` for briefing;
browse digests are authoritative for status/state. Budget ≈ (1 + number
of active Efforts) digest reads. A 12-run experiment across three model
families showed this roughly halves recall tool calls with no loss of
answer quality (Decision
`dec-adopt-a-bounded-status-briefing-fast-path-for-ef--kcw0rw39g3b2ym2h`).
2. **When a browse digest shows `[…truncated]` and you need the body:** run
`flatbread effort get <id>`, then Read/grep that digest (`artifact_path`)
for the full body. Reserve opening `.flatbread-efforts/**/*.md` for rare
cases (e.g. digest byte-cap miss on an oversized record), not normal
zoom-in.
3. **During work:** journal Findings as evidence lands; open Issues for real
gaps/blockers; record Decisions with `derives_from` citing the Findings,
Constraints, and Issues they respond to.
4. **On commitment:** `AcceptDecision` (mind `rejectSiblings`), `ResolveIssue`
with `resolvedBy` citing the closing Decision/Findings.
5. Maintenance: `flatbread effort cache prune` deletes digests older than
24h / over the 100 MiB ceiling.
64 changes: 64 additions & 0 deletions .agents/skills/effort-graph/glossary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Effort Graph glossary

The Effort Graph is persistent, queryable memory for long-horizon software
work. It builds on Flatbread's content vocabulary: each primitive is a
Collection, its instances are Records, and cross-primitive references are
Relations in frontmatter.

It is not a CMS, authoring UI, hosted memory product, or general task tracker.
Operational provenance (session, agent, model, DAG run) belongs in record
frontmatter; durable run transcripts live with Proof artifacts.

## Primitives

### Effort

The anchor for one coherent thread of work: a feature, migration, spike,
research investigation, or refactor. Every other primitive belongs to exactly
one Effort. It scopes bounded reads but carries only a short description; the
reasoning belongs in the related records.

### Issue

A tracked item needing attention: a question, defect, gap, or blocker. An Issue
is reactive. Decisions and Findings resolve it through lifecycle edges.

### Finding

A grounded observation about code, users, literature, or runtime behavior.
Findings cite evidence, resolve Issues, inform Decisions, surface Risks, and
may invalidate past Findings or Decisions. A retrospective Finding is evidence
gathered after a decision shipped.

### Decision

A commitment among alternatives. A proposed Decision is an active alternative;
an accepted Decision is committed; rejected, superseded, and deprecated
Decisions retain their lifecycle history. A Decision cites the Findings,
Constraints, and Risks it weighed rather than duplicating them.

### Constraint

A sticky hard or soft boundary that limits the decision space. Constraints are
known limits; they are not prospective negative outcomes.

### Risk

A prospective negative outcome with likelihood and severity. It is open,
mitigated by an accepted Decision, realized with evidence, or explicitly
accepted.

## Edges

`derives_from` is causal upstream evidence or context. `supersedes` replaces a
record of the same primitive, while `invalidates` says a record was wrong.
Those forward edges are authoritative; `superseded_by` and `invalidated_by` are
writer-materialized reverse projections. New edge vocabulary needs a
dogfooded query the existing vocabulary cannot express.

## Intentional non-models

Session, Run, Plan, Artifact, Agent, Investigation, Question, Proposal,
Retrospective, and Branch are not collections. Use provenance fields for
operational data; represent questions as Issues, proposals as proposed
Decisions, retrospectives as Findings, and branch history through Git.
166 changes: 166 additions & 0 deletions .agents/skills/effort-graph/reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Effort Graph — full API reference

Ground truth: the installed `flatbread` CLI and this reference (mutations),
reads, and configuration examples. Repository implementation files are not
consumer ground truth.

## IDs

Generated as `<prefix>-<slug>--<16-char-crockford>` with prefixes `eff`,
`iss`, `fnd`, `dec`, `con`, `rsk`. Filenames never define identity. Let the
writer generate ids; capture them from mutation results (`artifacts[0].id`
for creates).

## The 13 mutations (`flatbread effort write '<json>'`)

Common optional fields on all creates: `id`, `created_at` (ISO with offset),
`produced_in`, `created_by` (opaque provenance strings). Forward edge fields
on all creates except `CreateEffort`: `derives_from[]`, `supersedes[]`,
`invalidates[]` (arrays of existing ids; targets are validated).

### Effort lifecycle

```json
{"type":"CreateEffort","title":"...","body":"...","slug":"optional"}
{"type":"SetEffortStatus","effortId":"<eff-id>","status":"active|paused|completed|abandoned"}
```

### Creation (required: effort, title, body; initial state is derived)

```json
{"type":"WriteIssue","effort":"<eff-id>","title":"...","body":"...","kind":"question|defect|gap|blocker|<free-form>"}
{"type":"WriteFinding","effort":"<eff-id>","title":"...","body":"...","kind":"measurement|survey|dead-end|retrospective|<free-form>"}
{"type":"WriteDecision","effort":"<eff-id>","title":"...","body":"..."}
{"type":"WriteConstraint","effort":"<eff-id>","title":"...","body":"...","kind":"hard|soft"}
{"type":"WriteRisk","effort":"<eff-id>","title":"...","body":"...","likelihood":"low|medium|high","severity":"low|medium|high"}
```

Initial states: Issue `status: open`; Decision `state: proposed`; Risk
`state: open`.

### Edge retro-linking (records must already exist)

```json
{"type":"Supersede","supersederId":"<id>","targetId":"<same-kind-id>"}
{"type":"Invalidate","findingId":"<fnd-id>","targetId":"<finding-or-decision-id>"}
```

`Supersede` is same-primitive only and rejects an already-superseded target.
`Invalidate` asserts the target was wrong (stronger than superseded).

### Lifecycle transitions

```json
{"type":"ResolveIssue","issueId":"<iss-id>","resolution":"resolved|deferred|wontfix","resolvedBy":["<dec-or-fnd-id>"]}
{"type":"AcceptDecision","decisionId":"<dec-id>","rejectSiblings":false}
{"type":"MitigateRisk","riskId":"<rsk-id>","decisionId":"<accepted-dec-id>"}
{"type":"SetRiskState","riskId":"<rsk-id>","state":"realized|accepted","evidence":["<fnd-id>"]}
```

`AcceptDecision` with `rejectSiblings: true` (the default!) also sets every
other `proposed` Decision in the Effort to `rejected` with a back-pointer.
All mutations run in one journal transaction (save-or-undo).

### Mutation result

```json
{
"generation": "57",
"artifacts": [
{ "id": "...", "path": "decisions/....md", "operation": "created|updated" }
],
"touched": [{ "id": "...", "path": "..." }]
}
```

`generation` is a durable, monotonic journal token — the input to strict reads.

## The 5 read queries

All reads execute through Flatbread's query engine (in-process GraphQL over
the generated schema) and return a `ReadEnvelope`:

```json
{
"summary": "2 records; proposed 2; complete",
"artifact_path": ".flatbread/effort-graph/read-cache/<generation>/<query-hash>.md",
"artifact_sha256": "...",
"served_generation": "55",
"consistency": { "mode": "eventual|strict", "min_generation": null },
"page": { "returned": 2, "has_more": false, "next_cursor": null },
"hints": ["getRecord(\"dec-...\")"]
}
```

The digest at `artifact_path` is deterministic markdown: YAML query header,
anchor index, per-record sections (selected frontmatter, body, relation
lists), one-hop related records, and an edge table. Body policy:

- **`effort get`:** full record body (the normal zoom-in path).
- **`list` / `records` / `relations` / `blocking-decisions`:** body excerpt
capped at 600 chars / 12 lines (`[…truncated]`).

Caps: 25 primary records, one hop, 50 edges, 64 KiB; hitting a cap sets
`complete: false` with named `cap_reasons` — narrow the query or page rather
than expecting more. If a `get` body alone exceeds the 64 KiB digest byte
cap, the digest fails closed with a byte-cap banner (it does **not** fake a
full body via the 600/12 excerpt).

### Commands

```bash
flatbread effort get <id> [--resolve exact|head] [consistency flags]
flatbread effort list [--status active,paused,...] [--limit n] [--cursor c] [consistency flags]
flatbread effort records <effortId> [--kinds k1,k2] [--state s1,s2] [--status s1,s2] [--kind k1,k2] [--since iso] [--until iso] [--limit n] [--cursor c] [consistency flags]
flatbread effort relations <effortId> <fromId> --relations r1,r2 [--limit n] [--cursor c] [consistency flags]
flatbread effort blocking-decisions <effortId> [consistency flags]
flatbread effort cache prune
```

- `--kinds`: `effort|issue|finding|decision|constraint|risk` (records:
default all non-effort kinds).
- `list --status`: defaults to `active`; valid values are exactly `active`,
`paused`, `completed`, and `abandoned`. Values are ORed and results are
ordered by `created_at` ascending, then `id`.
- Filter semantics: AND across different flags, OR within a comma list.
`--since`/`--until` bound `created_at` (gte/lte, ISO strings).
- `--relations` values: `derives_from`, `supersedes`, `superseded_by`,
`invalidates`, `invalidated_by`, `rejected_by`, `mitigated_by`,
`resolved_by`, `evidence` (one hop, explicit only).
- `--resolve head`: follow `superseded_by` to the current tip; ancestors
render as checkpoint lines (max 5, then a count).
- `blocking-decisions` membership (frozen): Decision in the effort with
`state: proposed` whose `derives_from` directly contains an Issue in the
same effort with `kind: blocker` and `status: open`. For "what blockers
are open at all", use
`records <effortId> --kinds issue --kind blocker --status open`.

### Consistency flags

- `--strict-min-generation <token>`: serve at or after that journal
generation, or fail. `--timeout-ms <ms>` bounds the wait (default 3000).
- Errors (stderr JSON, exit 1): `EFFORT_GRAPH_GENERATION_WAIT_TIMEOUT`,
`EFFORT_GRAPH_INVALID_CURSOR` (cursor reused across a different query or
generation).

## Configuration surface

| Option | Where | Default | Notes |
| ---------------- | --------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Graph root | `effortGraphContent(root)` in `flatbread.config.js` | `.flatbread-efforts` | All six collection paths + refs derive from it; the preset must appear complete and unmodified for detection. |
| Config discovery | cwd of the CLI invocation | — | Exactly one `flatbread.config.*` must exist in cwd. |
| Digest cache | fixed | `<cwd>/.flatbread/effort-graph/read-cache/` | Generation-keyed; gitignore it. `cache prune`: >24h old deleted, then oldest-first to ≤100 MiB. |
| Journal | fixed | `<root>/.journal/` | Writer-owned; gitignored. Never edit. |
| Strict timeout | `--timeout-ms` per read | 3000 ms | |
| Page limit | `--limit` per read | 25 | Hard max 25. |

## What not to do

- Do not hand-edit record frontmatter or `.journal/`; bodies are freely
editable (the reindexer validates and repairs projections).
- Do not parse digest files as data feeds for other programs — they are
evidence for you to Read/grep; the envelope is the machine surface.
- Do not build polling loops around generations; strict reads wait
server-side.
- Do not model sessions/plans/agents as records — put provenance in
`produced_in` / `created_by` fields.
6 changes: 6 additions & 0 deletions .agents/skills/effort-graph/release.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"format": 1,
"flatbreadVersion": "1.0.0-alpha.22",
"effortGraphVersion": "0.1.0-alpha.0",
"gitTag": "v1.0.0-alpha.22"
}
Loading
Loading