Put the gist of the writing instructions in every agent session

Merged
JO
6d ago
+704-36

Context

The plugin's SessionStart hook (#1009) tells every session to run basecamp instructions before writing to Basecamp, and carries nothing else. On one machine, of 19 agent sessions that had that pointer and then posted to Basecamp, 8 never ran the command, 7 ran it through head -5 or tail, and 4 read it in full. The command printed its precedence preface first, four lines long, so head -5 showed the frame around the rules and none of the rules. Putting the full text in the system prompt was tried elsewhere and did not help either.

Tracked on the Kitchen card.

Change

  • basecamp instructions --brief prints each source (baseline, then profile) as its Source line, then one line per top-level list item: the bold text the item starts with, or else its first sentence. Headings, prose, code blocks and nested items are left out. JSON output carries the same lines as brief on each document, next to content.
  • The full output leads with the rules. Each source prints first (Source line, then text). The preface comes last, shortened to what it has to say: the sources are the operator's defaults and never come from Basecamp; the later source wins; a project's AGENTS.md wins on how posts look, the person on voice, and a single-post instruction over all of these; none of it overrides the skill.
  • The hook carries the gist. After the existing pointer it appends The gist (run the command for the full text): and the brief lines, but only when every source of the active profile can be read without the network: a file, or a URL's cached copy of any age. It never fetches, because the hook has a 5-second budget. With no cached copy yet, or no list to cut down, the pointer goes alone as before. The hook's app doesn't scope the cache dir by profile the way root does, so the gist looks for the cache under profiles/<name>. The hook's own state stays where it was.

For the coworker style guide (11 bold-led rules) the hook's whole context is 963 characters: the pointer plus 11 lines, about 85 words of rules.

Verification

  • Unit tests for the brief: bold lead, __ bold, bold wrapping a line, no bold (first sentence), bullets of every kind, 1) numbering, nested items, headings and prose, code fences, thematic breaks, no list, CRLF, terminal controls, clipping.
  • Output order: rules before the preface, baseline before profile, --brief text output, and brief in JSON.
  • Hook: a stale cached URL gives the gist, and the whole notice stays under 1,500 characters. No cache gives the pointer alone, and so does one of two layers uncached. Two file layers are labelled. A source with no list gives no gist. A profile-scoped cache is found. Every hook test swaps in an HTTP client and gh stand-in that fail the test if the hook fetches. With the gist disabled, the gist tests fail. With the profile scoping removed, the profile-cache test fails.
  • I ran the built binary against this machine's real config. --brief and the hook both print the 11 rules.
  • Local: go vet, gofmt, golangci-lint run ./internal/commands/, go test ./internal/commands/, make check-surface check-skill-drift test-sync-skills all pass. .surface was regenerated for --brief. The full bin/ci (e2e and the other gates) did not run locally because the machine's shared build lock was held, so this PR's CI covers the rest.

Alternatives

  • Full text in the hook: too long for every session's context. It also needs a fetch the hook can't wait for, and the full text in the system prompt didn't help elsewhere.
  • Fetching in the hook when there's no cache: risks the 5-second budget. The first basecamp instructions run fills the cache, and later sessions get the gist.
  • A brief maintained by hand in the source doc (front matter, say): exact, but it drifts from the rules. Deriving the brief from bold leads needs nothing from the doc's authors beyond the shape it already has.
  • Brief first, then the full text, in the plain command: helps head readers further but duplicates every rule. Left to --brief.