feat: refocus on drawings — code/ADA gated off, drawing-integrity wave, Brain-directed clarification
Docker Release / build-and-push (push) Successful in 1m8s
Docker Release / release (push) Skipped

- ENABLE_CODE_REVIEW flag (default off): skips code/ADA/jurisdiction review
  path in both pipelines; nothing deleted, one env flag to restore.
- Per-sheet Drawing Integrity QA wave (agent + classic, default on):
  dangling refs, on-sheet contradictions, dimension sanity, missing sheet
  essentials, tag hygiene. New DrawingIntegrityAgent + classic stage.
- Broadened conflict critic: intra-sheet + same-discipline contradictions,
  not just cross-discipline.
- Wave 6.5 Brain-directed clarification (bounded hub-and-spoke): Brain names
  uncertain findings, verify_evidence requests route through the wave-5b
  verifier; refuted findings suppressed. One planning call + capped verifies,
  single iteration. Shared _build_verify_scopes across 5b and 6.5.
- Config knobs, .env.example, frontend copy, tests (182 passing).
This commit is contained in:
2026-08-20 15:10:32 -05:00
parent bae608a505
commit d37ac8c1c7
22 changed files with 1917 additions and 63 deletions
+180 -38
View File
@@ -18,6 +18,9 @@ from backend.agents.extractors import (
SheetExtractorAgent,
SheetIndexAgent,
)
from backend.agents.integrity_agent import (
DrawingIntegrityAgent, build_integrity_scopes,
)
from backend.agents.linker import LinkerAgent, build_link_scopes, build_object_graph
from backend.agents.memory import ProjectMemory
from backend.agents.orchestrator import Orchestrator
@@ -174,11 +177,23 @@ def run_agent_pipeline(
memory.extend("findings", conflict_findings)
orchestrator.stage("Agent wave 5: scoped specialists")
code_results = orchestrator.run_scopes(
CodeAgent(usage),
build_code_scopes(sheets, jurisdiction, sheet_index),
config.AGENT_SPECIALIST_CONCURRENCY,
)
if config.ENABLE_CODE_REVIEW:
code_results = orchestrator.run_scopes(
CodeAgent(usage),
build_code_scopes(sheets, jurisdiction, sheet_index),
config.AGENT_SPECIALIST_CONCURRENCY,
)
else:
orchestrator.stage("[wave 5] code/ADA review disabled (ENABLE_CODE_REVIEW=0)")
code_results = []
if config.ENABLE_DRAWING_INTEGRITY:
integrity_results = orchestrator.run_scopes(
DrawingIntegrityAgent(usage),
build_integrity_scopes(sheets, page_to_b64, page_to_text),
config.AGENT_INTEGRITY_CONCURRENCY,
)
else:
integrity_results = []
construct_results = orchestrator.run_scopes(
ConstructabilityAgent(usage),
build_construct_scopes(clusters, conflict_findings),
@@ -197,7 +212,8 @@ def run_agent_pipeline(
)
specialist_findings = [
artifact
for result in code_results + construct_results + completeness_results
for result in (code_results + integrity_results
+ construct_results + completeness_results)
for artifact in result.artifacts
]
@@ -210,38 +226,12 @@ def run_agent_pipeline(
severities=config.AGENT_VERIFY_SEVERITIES,
)
target_indexes = {id(f): i for i, f in enumerate(specialist_findings)}
verify_scopes = []
for finding in verify_targets:
cited_pages = [
sheet_to_page[str(name)]
for name in (finding.get("sheets") or [])
if sheet_to_page.get(str(name)) in page_to_b64
]
images = [
page_to_b64[p]
for p in cited_pages[:config.AGENT_CONFLICT_MAX_IMAGES]
]
if not images:
continue # never judge evidence against images we could not load
# Text oracle: concatenated text layer of the cited sheets, capped.
excerpt = "\n\n".join(
f"--- Page {p} ---\n{page_to_text[p]}"
for p in cited_pages
if page_to_text.get(p)
)[:config.VERIFY_TEXT_MAX_CHARS]
if config.VERIFY_HI_DPI_CROPS:
images = _evidence_crops(finding, cited_pages, sheet_to_page,
page_words, page_to_b64, pdf_path,
fallback=images)
verify_scopes.append(AgentScope(
scope_id=f"verify:{target_indexes[id(finding)]}",
payload={
"finding_index": target_indexes[id(finding)],
"finding": finding,
"images_b64": images,
"text_layer_excerpt": excerpt,
},
))
verify_scopes = _build_verify_scopes(
verify_targets,
index_for=lambda f: target_indexes[id(f)],
sheet_to_page=sheet_to_page, page_to_b64=page_to_b64,
page_to_text=page_to_text, page_words=page_words, pdf_path=pdf_path,
)
verify_results = orchestrator.run_scopes(
EvidenceVerifierAgent(usage), verify_scopes, config.AGENT_VERIFY_CONCURRENCY)
suppressed = apply_verdicts(specialist_findings, verify_results)
@@ -286,6 +276,18 @@ def run_agent_pipeline(
1 for decision in decisions if decision.get("action") == "merged"
)
# Wave 6.5 — Brain-directed clarification (bounded hub-and-spoke). The Brain
# names kept findings it is unsure about; verify_evidence requests route
# back through the wave-5b verifier (fresh images + text-layer oracle).
# Refuted findings are demoted, dropped from `prioritized`, and moved to
# memory["suppressed"]. One planning call, one bounded verify wave, no loop.
if config.ENABLE_BRAIN_CLARIFY and prioritized:
prioritized = _brain_clarification_pass(
orchestrator, usage, memory, prioritized,
sheet_to_page=sheet_to_page, page_to_b64=page_to_b64,
page_to_text=page_to_text, page_words=page_words, pdf_path=pdf_path,
)
if require_review:
orchestrator.stage("Agent review gate: build human-review queue")
memory_snapshot = memory.snapshot()
@@ -331,6 +333,10 @@ def run_agent_pipeline(
1 for item in specialist_findings
if item.get("source_stage") == "code"
),
"drawing_integrity": sum(
1 for item in specialist_findings
if item.get("source_stage") == "drawing_integrity"
),
"constructability": sum(
1 for item in specialist_findings
if item.get("source_stage") == "constructability"
@@ -404,6 +410,10 @@ def run_agent_pipeline(
1 for item in specialist_findings
if item.get("source_stage") == "code"
),
"drawing_integrity": sum(
1 for item in specialist_findings
if item.get("source_stage") == "drawing_integrity"
),
"constructability": sum(
1 for item in specialist_findings
if item.get("source_stage") == "constructability"
@@ -440,6 +450,138 @@ def _dump(out_dir: str, name: str, value) -> None:
json.dump(value, f, indent=2)
def _brain_clarification_pass(
orchestrator,
usage,
memory,
prioritized,
sheet_to_page,
page_to_b64,
page_to_text,
page_words,
pdf_path,
):
"""Wave 6.5: let the Brain request targeted clarifications, execute the
verify_evidence ones through the wave-5b verifier, and prune refuted
findings out of `prioritized` into memory["suppressed"].
Bounded and non-looping: one Brain planning call, at most
BRAIN_CLARIFY_MAX_REQUESTS verifications, a single pass. Returns the
(possibly shortened) prioritized list. Any request type other than
verify_evidence is logged as planned-but-not-executed and left untouched.
"""
requests = BrainAgent(usage).plan_clarifications(prioritized)
if not requests:
return prioritized
by_id = {f.get("issue_id"): f for f in prioritized}
verify_findings = []
unsupported = 0
for req in requests:
if req.get("request_type") != "verify_evidence":
unsupported += 1
continue
finding = by_id.get(req.get("issue_id"))
if finding is not None and finding not in verify_findings:
verify_findings.append(finding)
orchestrator.stage(
f"Agent wave 6.5: Brain-directed clarification "
f"({len(verify_findings)} verify, {unsupported} other)"
)
if unsupported:
for req in requests:
if req.get("request_type") != "verify_evidence":
orchestrator.stats.failed_scopes.append(
f"brain_clarify:{req.get('issue_id')}: "
f"request_type '{req.get('request_type')}' planned, "
f"not executed (v1 supports verify_evidence only)"
)
if not verify_findings:
return prioritized
index_of = {id(f): i for i, f in enumerate(prioritized)}
verify_scopes = _build_verify_scopes(
verify_findings,
index_for=lambda f: index_of[id(f)],
sheet_to_page=sheet_to_page, page_to_b64=page_to_b64,
page_to_text=page_to_text, page_words=page_words, pdf_path=pdf_path,
)
if not verify_scopes:
return prioritized
verify_results = orchestrator.run_scopes(
EvidenceVerifierAgent(usage), verify_scopes,
config.AGENT_VERIFY_CONCURRENCY,
)
suppressed = apply_verdicts(prioritized, verify_results)
if suppressed:
suppressed_ids = {id(f) for f in suppressed}
prioritized = [f for f in prioritized if id(f) not in suppressed_ids]
existing = memory.snapshot().get("suppressed") or []
memory.replace("suppressed", existing + suppressed)
memory.extend("decisions", [
{
"finding_refs": [f.get("issue_id")],
"action": "dropped",
"reason": "Brain-directed clarification: evidence refuted on re-check",
"kept_issue_id": None,
}
for f in suppressed
])
return prioritized
def _build_verify_scopes(
targets,
index_for,
sheet_to_page,
page_to_b64,
page_to_text,
page_words,
pdf_path,
):
"""Build EvidenceVerifierAgent scopes for a set of findings.
Shared by wave 5b (severity-gated) and wave 6.5 (Brain-directed): each
finding's cited sheets are mapped to page images (hi-DPI evidence crops
when enabled, else full pages) plus a capped text-layer oracle. Findings
whose sheets resolve to NO loadable image are skipped (I2 guard) — never
judge evidence against images we could not load. index_for(finding) yields
the finding_index the verifier echoes back for apply_verdicts alignment.
"""
scopes = []
for finding in targets:
cited_pages = [
sheet_to_page[str(name)]
for name in (finding.get("sheets") or [])
if sheet_to_page.get(str(name)) in page_to_b64
]
images = [
page_to_b64[p]
for p in cited_pages[:config.AGENT_CONFLICT_MAX_IMAGES]
]
if not images:
continue # never judge evidence against images we could not load
# Text oracle: concatenated text layer of the cited sheets, capped.
excerpt = "\n\n".join(
f"--- Page {p} ---\n{page_to_text[p]}"
for p in cited_pages
if page_to_text.get(p)
)[:config.VERIFY_TEXT_MAX_CHARS]
if config.VERIFY_HI_DPI_CROPS:
images = _evidence_crops(finding, cited_pages, sheet_to_page,
page_words, page_to_b64, pdf_path,
fallback=images)
finding_index = index_for(finding)
scopes.append(AgentScope(
scope_id=f"verify:{finding_index}",
payload={
"finding_index": finding_index,
"finding": finding,
"images_b64": images,
"text_layer_excerpt": excerpt,
},
))
return scopes
def _evidence_crops(
finding: Dict,
cited_pages: list,