Compass Query Shapes Current state, recognition boundary, and escape hatches
Architecture review

Where Compass turns messy requests into executable work

A visual walkthrough of the current backend shape, how planning recognition changed the answerability boundary, and why issue #782 now matters.

Current read

Compass is not one open-ended agent. It is a typed pipeline: Planner drafts a shape, recognition can advise, CatalogResolver owns execution IDs, Executor reads rows, and Renderer/Quality surface evidence.

Why this deck exists

Natalie's core question exposed the first design tension: should catalog awareness happen before execution? PR #753 adds that advisory layer. PR #804 shows the next rule: selected guidance may shape planner validation, but raw prose must not become a second dispatcher.

planning recognition query shape escape hatches
Source baseline: origin/main refreshed at 28909809 after PR #804; PR #753 planning recognition, PR #804 peer guidance, and issue #782 guardrails reviewed.
The question underneath the transcript

Where does Compass decide a request is answerable?

The team already agrees at 30,000 feet: use AI for meaning, then deterministic code for truth. The hard part is deciding where meaning becomes executable authority.

1. Query logic

Can Compass understand the research plan? Example: "show starting salary for districts with highest FRPL share" is not just a salary lookup.

2. Entity resolution

Can Compass map user terms to governed catalog objects? Example: "starting salary", "Philadelphia", "observations", "FRPL".

3. Data coverage

Once the plan and entities are resolved, does the covered universe actually contain current reviewed rows and citations?

The useful distinction

A bad answerability outcome can come from the planner choosing the wrong shape, the resolver missing the right entity, the executor not supporting the shape, or real coverage gaps. Those are different bugs.

Current state

The backend is a turn pipeline, not one agent graph

Each handoff has a typed contract. That contract is how Compass keeps the answer from becoming free-form model prose.

Planner Owns route and typed query shape: direct, clarify, execute, or policy_guidance.
Recognition Advisory catalog awareness from draft plan phrases. Candidate labels, blockers, and actions only.
Normalizers Merge session context and promote pending clarification. This layer should use typed context, not raw-message rescues.
Executor Compiles the QueryPlan into deterministic operation work.
Catalog Resolves phrases into approved metric IDs, district IDs, profile fields, peer sets, or blockers.
Renderer + Quality Render validated artifacts, then write non-blocking verdicts after response assembly.

Important property

Executed data answers are rendered from `ResultSet` and `ValidationAuthority`, not composed freely by the LLM.

Important nuance

Recognition can show candidate labels early, but final catalog resolution still owns whether requested nouns become execution authority.

The known shapes

Compass has a small set of executable templates

Think of these as the current work orders the backend knows how to carry out safely. The full shape is the whole `QueryPlan`, not just the operation name.

lookup

Exact selected-district or scoped metric table.

Example: salary for Dallas, or all districts with metric X.

rank

Top, bottom, ordered, or sorted district rows.

Example: highest starting salary.

count

Denominator, threshold count, or categorical breakdown.

Example: how many districts offer X?

trend

Longitudinal or multi-year metric rows.

Example: five-year salary trend.

profile_lookup

NCES/profile facts for selected districts.

Example: enrollment or FRPL for Denver.

similarity

Peer discovery without a policy metric comparison.

Example: who are Denver's peers?

peer_comparison

Anchor plus deterministic peers, compared on a governed metric.

Example: Denver peers on max salary.

policy_guidance

NCTQ stances, rationales, and exemplars from the typed library.

This is a route, not a district-data operation.

Authority boundaries

Each layer should own exactly one kind of decision

This is the chain-of-custody model that keeps Compass from drifting into confident but ungrounded answers.

Shape Planner Route plus typed `QueryPlan`: operation, selection, metrics, filters, sort, limits, output. Does not resolve catalog IDs.
Advisory recognition PlanningRecognitionAdvisor Candidate labels, ambiguity, unsupported concepts, and recommended clarify/refuse/finalize actions. Does not expose execution IDs or read rows.
Entity authority CatalogResolver Metric IDs, district IDs, profile fields, peer sets, unsupported blockers, and clarification candidates. Should not invent new shapes or hide missing catalog data in Python.
Execution Executor Supported operation predicates, DB reads, coverage rows, result artifacts. Should not reinterpret prose.
Surface Renderer / Quality User-facing explanation, tables, sources, validation and verdict records. Should not change underlying data truth.
Natalie's resolution question

Resolution now has an advisory pass and an authoritative pass

PR #753 moved catalog awareness earlier, but kept the authority boundary intact: recognition can advise; execution still resolves.

Stage
What happens today
Why it matters
Planner draft
Emits phrase-level specs: `MetricSpec(name="starting salary")`, `SelectionSpec(districts=[...])`, filters, sort, output.
Good for safety; it avoids fake IDs while giving recognition a typed plan to inspect.
Planning recognition
Extracts typed phrases and returns advisory candidate labels, ambiguity, unsupported concepts, and recommended actions.
This is the new early warning layer. It can observe, intercept, or trigger a finalize pass depending on mode.
Final planner pass
In `finalize` mode, the Planner can consume redacted recognition facts and produce a final `PlannerTurn`.
The Planner sees labels and blockers, not execution IDs. It still cannot mint data authority.
Executor + Resolver
Re-resolves the final plan through CatalogResolver for approved IDs, fields, peer sets, or blockers.
This is where "do we have it?" becomes authoritative before rows are read.
Clarify / refuse
If recognition or final resolution finds a hard blocker, Compass should clarify or refuse before rendering.
Issue #782 asks us to retire paths that answer, repair, or soften outside this chain.
Answerability is not one bug class

When Compass misses, locate the miss before fixing it

The same user-visible failure can come from four different layers. Fixing the wrong layer adds more brittleness.

1

Shape miss

Planner picked an operation the executor cannot compile, or treated a multi-step research plan like a simple lookup.

2

Resolution miss

Metric, district, profile field, or peer phrase did not map to the governed catalog object even though the data exists.

3

Context miss

Follow-up turn lost prior result rows, prior metric, pending clarification, or state narrowing.

4

Coverage miss

The plan and entities are right, but reviewed rows are missing, stale, not applicable, or outside the Pathfinder.

Working rule

Before adding a special case, classify the failure by layer and by authority: selected guidance and catalog aliases are safer; raw-message code that rewrites execution behavior is the issue #782 escape-hatch risk.

What is strong in the current setup

The conservative architecture is worth preserving

The current design has real strengths. The problem is not that the system is typed. The problem is where we put the next layer of meaning.

Keep
  • Typed `PlannerTurn` and `QueryPlan` contracts.
  • Planning recognition as an advisory-only early warning layer.
  • Catalog authority for IDs and approved fields.
  • Bounded candidate-only adjudication.
  • Deterministic renderer over validated `ResultSet` artifacts.
  • Persisted turn snapshots and resolution reports for forensic review.
Do not overlearn
  • Do not solve every failure with phrase heuristics.
  • Do not let the model invent metric IDs or unsupported research operations.
  • Do not collapse catalog ambiguity into a confident answer.
  • Do not accept a user constraint and merely disclose later that it was not enforced.
  • Do not judge readiness from aggregate score alone.
What is brittle today

The shape system is stronger, but boundary drift is still the risk

Issue #782 reframes the risk: not wild hallucination, but second paths that can answer, repair, soften, or score outside the clean chain.

Mode-dependent recognition

Planning recognition exists, but behavior depends on `COMPASS_PLANNING_RECOGNITION_MODE`: off, observe, intercept, or finalize.

Validator boundary

Selected guidance metadata can require typed planner behavior. Validator code should not re-read the raw user message to dispatch below the planner.

Shadow catalog authority

Catalog aliases, unsupported concepts, and contextual defaults should stay in reviewed catalog data, with startup/smoke checks for expected launch rows.

Accepted but unenforced constraints

A user filter should be enforced before rendering or turned into a clarification/refusal, not answered with a later caveat.

Soft launch evidence

Non-blocking verdicts are fine for chat UX, but controlled sweeps must show trace gaps, verdict-write failures, and skipped rows explicitly.

Shape logic spread

Planner prompts, selected guidance, recognition, validators, executor predicates, catalog aliases, and tests still encode pieces of shape knowledge.

Issue #782 register

The register now separates retired debt from relapse points

The deck is aligned only if it names what PR #804 retired and what still needs guardrails before Compass can close issue #782.

1. Launch rescue debt

`launch_rescue.py` is removed on current main. Do not recreate it; route replacement behavior through planner guidance, catalog, execution, or refusal.

2. Answerability recovery debt

`answerability_recovery.py` is removed on current main. Future repairs need a typed owner and scenario evidence.

3. Prose dispatch in validators

Selected guidance metadata and typed `PlannerTurn` fields are allowed. Raw `frame.message` checks below snippet selection are not.

4. Shadow catalog authority

Aliases, blockers, and defaults belong in `compass.catalog_aliases` / governed seed and migration data, not hidden Python constants.

5. Code-owned blockers

Unsupported concepts should be governed product data, not scattered constants.

6. Weak candidate recall

Safe adjudication still fails if the right governed candidate never appears.

7. Unenforced constraints

`metric_value_filter_not_applied` is the symptom class: accepted filters must be enforced or refused.

8. Soft evidence failures

Verdict, trace, envelope, and denominator gaps must show up in launch evidence.

9. Route boundaries

`direct` and `policy_guidance` need guards against bypassing governed data routes.

Audit rule

Safe paths add advisory context or candidates. Risky paths rewrite the user request, choose execution behavior, hide missing catalog authority, or pretend an unenforced constraint was applied.

Ways to move forward

After PR #804, the question is guardrails and coverage

The next move is not simply "more AI or more code." It is to keep selected guidance narrow, move recognition gaps into governed catalog data, and block raw-message relapse.

Option A Use selected guidance narrowly

Guidance can require a typed operation such as `peer_comparison`; it should not become hidden execution authority.

Option B Keep raw-message rescue retired

Do not reintroduce launch rescue or answerability recovery under a new name when a prompt fails.

Option C Move authority to governed data

Catalog aliases, unsupported concepts, and defaults should live in reviewed data, not hidden Python constants.

Option D Create a capability and hatch ledger

Document supported request shapes and every remaining workaround, then classify each as keep, move, repair, or delete.

Pragmatic recommendation

Start from issue #782: keep planning recognition advisory, let selected guidance validate typed shape only, use final CatalogResolver execution as the authority boundary, and require scenario evidence for every repair.

Questions for Macon, Natalie, and Justin

Decisions to make before the next implementation wave

These are product/architecture choices, not just code cleanup tasks.

Which recognition mode is live?

Are we evaluating off, observe, intercept, or finalize behavior when we judge answerability?

What counts as a relapse?

Is a path advisory candidate broadening or selected-guidance validation, or can it change execution behavior by rereading raw prose?

What moves to catalog?

Which aliases, unsupported concepts, and governed defaults should become reviewed data instead of Python constants?

What belongs in typed repair?

If Compass needs a second pass, what typed inputs, authority limits, and tests prevent it from becoming launch rescue again?

Which constraints hard-fail?

If Compass accepts a filter or condition, should failure to enforce it always clarify or refuse before rendering?

What proof closes #782?

Which traces, scenario families, and tests show that a workaround was removed without losing the intended behavior?

What guard blocks relapse?

Which tests or lint checks prevent the next urgent bug fix from adding raw-message dispatch again?

Suggested next work package

Turn issue #782 into measurable guardrail work

Use the issue as the shared artifact. Link every PR, scenario, debug trace, and reviewer note until each remaining risk has an owner and a disposition.

Issue spine

Remove escape hatches without weakening the chain of custody.

  • Mark launch rescue and answerability recovery as retired by current main.
  • Document remaining relapse points in one register.
  • Classify each as keep, move to catalog, move to typed repair, or delete/refuse.
  • Record which layer currently owns the behavior and which layer should own it.
  • Keep the safe-vs-risky distinction explicit: advisory guidance is not execution authority.
  • Link PRs as attempts with evidence, not as closure by themselves.
Implementation spine

Move behavior into governed layers one family at a time.

  • Run affected scenarios whenever selected guidance, catalog aliases, or typed execution support changes.
  • Move aliases, unsupported concepts, and contextual defaults into governed catalog data.
  • Do not recreate deleted rescue families under new helper names.
  • Make accepted filters enforced-or-refused, never disclosed as not applied.
  • Make trace/verdict gaps visible in controlled sweeps and add guards against new raw-message dispatch.
Release-note format for Natalie

We now do X. We changed Y files/layers. This fixes prompts like Z. Still open: A/B/C. Current verification: scenario/debug links and exact pass/partial status.