Lean-Context Architecture (Two-Tier Progressive Disclosure)
Source Specs:lean-context-prompt.md,v2-cache-harness-prd.md, the engine plugin ecosystem notes
Picasso builds agent context using two-tier progressive disclosure so every turn sends only what that turn needs while preserving a 100% byte-stable prompt-cache prefix across turns, permission modes (default / plan / auto), and surfaces (apps/cli + packages/tui and apps/mac + apps/desktop-helper).
Nothing is disabled or deleted: every tool, skill, memory, and MCP server remains enabled and discoverable, costing 0 payload tokens until requested on demand.
---
1. Tier 1 vs. Tier 2 Split
Tier 1 — Always-On Routing Index (Byte-Stable Prefix)
Tier 1 sits in the static request prefix (tools -> system -> initial instruction index) and must be byte-identical across turns and modes within a session:
- System prompt & invariants: Deterministic ordering, zero wall-clock timestamps, zero per-turn dynamic state.
CANONICAL_SECTIONSreplace every engine section, so they restate the engine's behavioural rules (read-before-write, exit codes, background jobs, parallel subagent delegation, todo/skill/ask-user use, the goal blocker policy, and when to reach for each deferred tool family). Order:harness:identity("You are Picasso"; never names the engine underneath) →deployment:persona-prefix→style:response(outcome-first markdown that renders in the dense Mac/TUI views: short headings, bullets, status tables, fenced code, each file named once as inline code for the Files row; a file just written is named, not pasted back into the reply; one status line before tool batches) →practice:engineering(parallel independent calls, conventions and existing dependencies, root cause with ~3 bounded attempts, environment issues reported, git and destructive-action safety, no secrets) →context:file-reference→tool:core→lean:gateways. Agent-scoped engine text a child or teammate depends on (tool:structured_output,subagent:delegation,tool:*:depth, a teammate'steam:identity/team:policy) is kept for that agent only. - Instruction index (`AGENTS.md`): Target ≤ 4 KB (hard cap
maxBytes: 8192inagent-instructions). Contains project build/test commands, workspace layout, core invariants, and aRead docs/<topic>.md when working on <scope>routing table.CLAUDE.mdandPICASSO.mdare single-line@AGENTS.mdpointers so instructions are never duplicated. - Core tool schemas (≤ ~12 tools): High-frequency tools (
bash/pwsh,fsread/write,fs-search,str-replace-editor,todo,ask-user,skill,subagent,remember/recall) plus the fixed gateway tools (tool_search,tool_call, and per-MCP<server>_call). - Skill index: Each enabled project/curated skill injects
name+ dense ≤ 200-char trigger description. Global~/.claude/skillsare listed by name only or collapsed behindskill_searchonce the index budget (2 KB) is reached.
Tier 2 — On-Demand Payloads (Appended History)
Tier 2 payloads cost 0 prefix tokens until triggered and always enter as appended conversation history (tool results or sourced agent/pre-step user-role reminders):
- Deferred tools (~28+ ecosystem tools): Worktree, agent-team, terminal, session-query, workflow, goal, ralph, present, web, subagent_fork/report/control, etc. Invoked via
tool_call(name, arguments)after discovery viatool_search(query)or~/.picasso/tool-catalog/<group>.md. Argument validation errors automatically append the tool's cached input schema for self-correction. An exact-name search (or one with at most 3 matches) returns the engine's full description, input schema and the engine usage rules for that tool family (ENGINE_TOOL_GUIDANCE; the live section text when captured), because Tier 1 replaces the engine's prompt sections. - MCP server catalogs: Each configured MCP server exposes a single
<server>_call(tool, arguments)gateway tool (~100–200 tokens). Passingtool="__list__"/tool="__describe__"or reading~/.picasso/mcp-catalog/<server>.mdloads schemas on demand; the server process spawns lazily on the first realtools/call. - Skill bodies (`SKILL.md`) & rules: Loaded only when
tool-skillis called or when the agent reads the file from a trigger inAGENTS.md. - Memory bodies (`packages/picasso-memory`): Pinned memories and
style/workflowpreferences inline in Tier 1 reminders; all other recalled memories appear as compact index lines (id · class · title · scope, ≤ 1 KB) and load full bodies viarecall(ids | query)with session deduplication. - Stale-todos nudge: when the last
todo_writestill has open items and the model has moved on (≥ 20 tool calls, or a new goal round / compaction since the write),agent/pre-stepappends one sourcedstale-todos<system-reminder>at the tail of the step's messages, asking it to update or replace the list. At most once per 20 tool calls and twice per write; the text depends only on the todo list, so identical history yields identical bytes. - Mode-specific prompt sections & runbooks:
plan-mode.sectionand domain runbooks (docs/*.md) enter as appended history when the mode or task triggers them, never by mutating the system prompt ortoolsarray mid-session.
---
2. Prompt-Cache Stability Invariants
- Never mutate `tools` mid-session: Adding or removing tool definitions invalidates the entire provider KV cache (
tools -> system -> messages). All deferred and MCP tools route through fixed-schema gateway tools. - Identical Tier 1 across modes: Switching between
default,plan, andautomodes keeps the exact same tool catalog and system prefix; mode transitions append state notices viaagent/pre-step. - Cross-surface parity:
apps/cli(packages/core/src/dsh/runtime-session.ts) loadsdsh/picasso-ecosystem.cordis.patch.ymlanddsh/picasso-sdk.patch.yml;apps/desktop-helper(its engine host module) does not: it composes the engine's base and web-app bundles plus generated rows. Both surfaces share thepackages/picasso-*plugins. Tier 1 byte-equality across surfaces therefore depends on the two compositions exposing the same core tools and system layers, and is a property to verify (withpicasso-cache-probeand the Tier-1 hash tests), not one the shared patch files guarantee. The generateddsh/tool-catalogis derived from the CLI patch only, so it can advertise deferred tools the Mac does not mount until the matching Mac plugin rows land. - Verification: Use
packages/picasso-cache-probeandpackages/cache-harness(seedocs/cache-harness.md) to assert zero cached-prefix drops on turn 2+ after an on-demand Tier 2 load and ≥ 85% reduction inhellofirst-turn prefix bytes againstreports/lean-context/baseline.md(the unit-level no-MCP floor inpackages/picasso-lean-contextis 83%: the ~2.1 KB style/practice sections were bought with budget, not by cutting tool rules).
---
3. /context Inspector (Observational Telemetry)
packages/picasso-lean-context exports a ContextStats collector and a Config schema that publish four `.volatile()` fields under the picasso-lean-context settings namespace. settings/describe projects them as the namespace value — the read path the Mac app's Context inspector sheet uses (RelayClient+Context.swift → remote("settings/describe") → ns == "picasso-lean-context").
| Field | Content |
|---|---|
registry | Tier-1 wire tool count, deferred tool count, MCP gateways + server names, known skills — refreshed at each system-prompt/assemble and agent/pre-step. |
snapshot | The last pre-step normalisation: message count, instruction and skill-catalog before/after bytes + cap flags, SHA-1/pointer dedupe drops, injected plan-mode/runtime-policy/stale-todos reminders, skillsKnown, sessionId. |
counters | Cumulative totalSteps, instructionsCapped, skillsOverflowed, dedupeRuns, remindersInjected. |
events | Compression journal, newest first, ring-capped at CONTEXT_STATS_EVENT_LIMIT (50). |
Observational-only invariant: every capture point runs *after* the normalisers have already selected their outputs (ContextStats.recordStep reads normalizePreStepMessages's additive stats return; captureRegistry reads the post-sort wireMap/deferredTools/mcpServers sizes). Nothing feeds stats back into decision.messages, assembly.tools, or sections — so a decision's emitted bytes, tool schemas, and ordering are untouched whether stats are collected or not. Without the engine's schema library, Config is undefined and capture still works for deps.stats consumers; the Mac sheet renders a named .unavailable state when the namespace is absent.
Entry points: View › Context inspector, ⌃⌥C, or the command palette's "Context inspector" action (ButtonActionID.openContextInspector → NativeSheet.contextInspector).
---
4. Context timeline (/context timeline, picasso context timeline)
The @picasso/lean-context/timeline fold turns a session log into a per-turn timeline: what entered the context and its size, each model step's cache hit and miss, what compaction removed or summarised, and one lane per subagent. The SDK server serves it as session/context/timeline (packages/picasso-sdk-server/context-timeline.js) for the TUI's /context timeline; picasso context timeline [thread] [--last] [--json] folds the same thing from a saved session's log with no engine running and no model call (apps/cli/src/context-timeline.ts). Picasso for Mac shows it under the Context tab's Overview / Timeline switch. A field the engine did not record prints as not recorded (the Mac app says "No usage reported" or "Size not recorded"), never as a guess (C1).