Writing signs

A sign is just-in-time guidance for an area of your codebase. It steers; it never blocks. You write signs as type: sign entries in a bundle’s signposts: list.

Recap from concepts: a sign is delivered the moment the agent reads a file in its area, and re-injected as the context grows, so it stays present without bloating the agent’s starting context. Think flashlight, not rulebook.

The shape

Signs live in a bundle’s signposts: list, each tagged type: sign (a sign and the rule it explains sit together in that one list). Your own ad-hoc signs live in the local bundle; a shared set lives in its own bundle.

local:
  title: Local guardrails
  summary: Ad-hoc signs and rules for this repo.
  signposts:
    - type: sign
      id: db-area                 # a short unique name
      at: [touch]                 # the moment it fires
      on: ["src/db/**"]           # the area this lights up for
      text: |-
        Queries go through the repository layer, never inline in a route.
        Migrations are append-only — never edit a shipped migration.
Field What it does
bundle The top-level block it lives in (local, fcis, …) — a title, a summary, and a signposts: list. A bundle is what install and propagate move as a unit.
type sign (steers) or rule (blocks).
id A short unique name for the sign.
at The moment it fires — touch (on the area) or session (orientation, below). Always explicit.
on The path pattern(s) that trigger a touch sign. When the agent reads/touches a matching file, it’s injected.
text The guidance itself — kept short and specific to this area.
file Instead of text:, inject the contents of a repo file (handy for a longer project brief on a session sign).

Orientation — the composed CLAUDE.md (at: [session])

Every bundle may carry one at: [session] sign — a few terse lines of orientation. At session start (and after resume, /clear, and a compaction) the engine injects the concatenation of them all. The opening context an agent sees becomes the sum of the bundles the project adopted: install the security bundle and “the .env files are encrypted, read them freely” appears in every session; drop the bundle and the line goes with it. Nothing to keep in sync by hand, and it travels on cherry-pick — the reason this beats writing a managed block into CLAUDE.md.

core:
  title: Core
  summary: Cross-cutting orientation and guards.
  signposts:
    - type: sign
      id: orientation
      at: [session]
      text: |-
        docs/arch/ is the system prior — read before structural work.
        Repeated commands live in justfile recipes, never package.json scripts.

Discipline: one session sign per bundle, a few lines each — orientation says where and what regime; depth stays in the area signs that fire just-in-time. The report card tracks the composed size so it can’t creep silently.

The re-injection cadence

How often a sign is refreshed as the conversation grows is set once, in config::

config:
  drift_tokens: 200000     # re-inject a sign after ~this many tokens since it was last seen

Lower it if your agent drifts quickly on long sessions; raise it if signs feel repetitive. A single sign can override the global cadence with its own drift_tokens.

Overlapping areas — both apply

Areas can overlap, and that’s by design. When a file matches more than one sign — a broad one on a parent folder and a finer one on a child — both are injected, in the order they appear in signposts.yml. There’s no “most specific wins”: the agent gets the area’s general guidance and the specific sign layered on top.

local:
  title: Local guardrails
  summary: House-style signs.
  signposts:
    - type: sign
      id: src-area
      at: [touch]
      on: ["src/**"]
      text: "House style: small functions, no default exports."
    - type: sign
      id: db-area
      at: [touch]
      on: ["src/db/**"]
      text: "Queries go through the repository layer; migrations are append-only."
Agent touches… Injected
src/app/page.ts src-area
src/db/users.ts src-area + db-area — both, in YAML order

Each sign keeps its own re-injection clock — it reappears on first touch, then only after its drift_tokens have passed since it was last shown — so overlapping signs don’t all re-fire together. Two consequences worth knowing:

  • Layer deliberately. Put broad house-style on a parent area and specific guidance on child areas; a deep file usefully accumulates both.
  • Keep signs short. Because a deep file can pick up several at once, length adds up — scope each sign tightly to its area.

Writing good signs

  • One area, one sign. Keep each scoped to the folder it’s about — that’s what makes delivery just-in-time instead of a wall of text.
  • Shape and judgement, not bans. “Mind the shape here” belongs in a sign. “This is never allowed” belongs in a rule — where a block can carry the same message at the moment it’s needed.
  • Short. A few lines. It’s competing for the agent’s attention with the task.
  • Let irrelevant ones sleep. A sign for an area the agent never touches costs nothing — no need to prune for projects that don’t use that area.

Sign or rule? If the agent must not do something — it’s costly, irreversible, or has one correct form — reach for a rule instead (or as well). Signs give the chance to get it right; rules guarantee it.