.agents/contexts/pieces/docs/adr/0001-piece-set-visibility-derived-at-read.md
A Piece Set's config stores an include/exclude selection, and visibility is computed when pieces are listed — never written when a piece is installed.
PieceSetConfig = {
pieces: { mode: 'include_all' | 'exclude_all'; exceptions: string[] }
selectedActions: Record<pieceName, string[]> // presence ⇒ "selected"; absent ⇒ "all"
selectedTriggers: Record<pieceName, string[]>
}
include_all → visible = everything (present + future) minus exceptions; exclude_all → visible = only exceptions. This subsumes the old set-level includeNewPieces boolean (now pieces.mode).@activepieces/shared (isPieceVisible, isComponentVisible), imported by both the server filter layer and the web UI.The previous model was a pure deny-list (disabledPieces/disabledActions/disabledTriggers) plus policy flags (includeNewPieces, curatedPieces). A deny-list cannot declaratively express "hide things that don't exist yet," so the code answered that imperatively: a pieceHooks.onPieceCreated hook fired on every piece-metadata create — including the hourly PIECES_SYNC cron across all platforms — and walked every piece set in batches, materializing new pieces/actions into the deny-lists. A set with auto-include off accumulated the entire catalog into disabledPieces over time.
Storing the visible allow-list instead makes "new = not in the list = hidden" automatic. The whole write-on-install path is deleted: handleNewPieceInstalled, computeConfigForNewPiece, the PieceHooks interface and its EE wiring, and the per-version action/trigger diff in pieceMetadataService.create. The hourly sync no longer touches piece_set; managed-authn no longer enumerates the catalog to invert an allow-list.
mode (there is a genuine set-level "auto-include new pieces" policy); components are a bare allow-list (presence = curated) with no mode field. The presence/absence of a component key is the mode.UpdatePieceSetRequestBody sends pieces (full replace) and actions/triggers as a per-piece ComponentIntent ({mode:'all'} deletes the key; {mode:'selected', selected} sets it). This replaced 8 imperative enable/disable/curate ops and let the web visibility sheet drop its delta computation. An empty selected array is a valid "hide all of this piece's components" state and is never normalized to a delete.disabledPieces growth — the core costs.{mode, exceptions} at every scope (components included). Rejected: components are strictly binary ("all" vs "selected"), so a component mode field would always be dead weight.