Repoint RFI extraction off sage to a large world-knowledge model (Synthetic syn-large-vision) — conduit-in-slab RFI misrouted to electrical #9
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Live demo with Jake Marovich (2026-08-21, ~17:12–18:01 in the call recording) surfaced a
discipline-classification failure that a bigger world-knowledge model would not make.
The bot assigned an RFI to Brian Nguyen — Lead Electrical, ACE MEP Group. The RFI actually
asked how large a conduit pipe may run through the lower mat slab. That is a structural
question (ZFA Structural Engineers), not an electrical one.
Jake's diagnosis, verbatim from the transcript:
Root cause
The model keyed on the noun ("conduit" → electrical) and missed the governing relationship
("conduit penetrating a structural mat slab" → the structural engineer owns the penetration,
because it affects rebar, cover, and slab capacity). Correct routing requires construction-domain
world knowledge, not keyword association.
sage(Qwen3.6-27B) does not reliably carry it.This is not a prompt bug. Discipline routing is the single highest-consequence field the extractor
produces — a misrouted RFI goes to someone who "won't really care," burns response-time SLA, and in
a legally-material workflow that delay is exactly what immunizes a subcontractor.
Requested change
Repoint RFI extraction from sage to
syn-large-visionon Synthetic (operator directive,2026-08-21).
Affected code
config.toml:13sage_base_url = "https://sage.symbiotrip.com"config.toml:20sage_model = "sage"src/config.rs:52-53,108sage_modelfield +default_sage_model()src/sage.rsCHAT_COMPLETIONS_PATH, bearer auth viaSAGE_API_KEYThe client already speaks OpenAI-compatible
/v1/chat/completions, so the transport needs norework — this is an endpoint, model-id, and credential change plus verification.
Blocking constraint — do not skip
The fleet's Synthetic provider is configured against a tailnet address
(
http://100.64.0.1:8011/v1). constructionbot runs in the gal tenant container and must neverroute through the tailnet — tenant traffic reaches external model backends over public HTTPS
only. That is why
sage_base_urlishttps://sage.symbiotrip.comtoday and not a100.64.x.xaddress.
Implementation must therefore resolve a public HTTPS endpoint for Synthetic before any config
change lands. Pointing at
100.64.0.1:8011will fail closed in the container.Open questions (resolve before implementing)
syn-large-visionis not one of the four models the fleet's Syntheticprovider currently lists. Confirm the upstream model identifier Synthetic serves it under.
declaring for the gal tenant?
secret/gal/synthetic/credentials); theclient reads
SAGE_API_KEYtoday — see naming below.-visionimplies image input. RFI attachments are PDFs handled today viapdftotext. Should the extractor start sending page images for markup-heavy RFIs? That is alarger change than a model swap and probably belongs in its own ticket.
Acceptance criteria
tests/fixtures/calibration.yamlas a regression fixture with expecteddiscipline_guess = structural.Morella samples.
scripts/no_hardcoded_endpoints.shstill passes.Follow-on (separate ticket, not this one)
Once the backend is provider-neutral, the
sage_*config keys andsrc/sage.rsmodule name becomeactively misleading. Suggest renaming to
llm_*/src/llm.rswith sage as one selectable backend.Kept out of scope here to keep the repoint small and revertible.
Filed from the 2026-08-21 demo call. Full transcript in the gal workspace at
meetings/2026-08-21-constructionbot-demo-wishlist-roadmap-review-jake/.Open questions 1–3 resolved — verified live, 2026-08-21
The blocking constraint clears. synthshim already provides exactly the public HTTPS front end
this needs (spec 100,
symbiotrip/synthshim).syn:large:visionGET /v1/modelsthrough the shim; 11 models served, thesyn:*family exposeslarge:text,large:vision,small:text,small:visionhttps://synthetic.symbiotrip.com/v1synthetic-shim.serviceactive on sage; unauthenticatedGET /v1/modelsreturns 401 (auth enforced, service reachable)/run/openbao/synthetic-shim-beareron sage; needs writing into gal OpenBao for the tenant container to fetchWhy this satisfies the tenancy constraint
synthetic.symbiotrip.comis a public HTTPS endpoint, structurally identical to how the tenantalready reaches
https://sage.symbiotrip.com. No tailnet involvement. The shim listens on127.0.0.1:8010on sage with nginx/ACME in front; the tenant container talks to the public name.The shim is OpenAI-compatible and proxies
/v1/chat/completionsand/v1/modelsupstream tohttps://api.synthetic.new/openai/v1, selecting whichever pooled key has the most remaining quotaheadroom per request. It applies no model allowlist — model ids pass through — so
syn:large:visionneeds no shim-side change.
Net effect on this ticket: the change is
sage_base_url→ the shim,sage_model→syn:large:vision,plus a bearer in gal OpenBao. The client code needs no transport rework.
Two things the implementer must carry, not discover
1. Interim spend lands on DeepNet's pooled quota. The shim pools symbiotrip-side Synthetic keys.
Until Gallaher has their own key, every RFI extraction draws down the shared pool. Operator direction
(2026-08-21): acceptable as an interim — "we will need to set them up with their own api key, but for
now they can use synthshim." Treat it as a subsidised bridge with an explicit end, not a resting state.
2. The shim has ONE shared client bearer, fleet-wide.
shim/src/auth.rscompares the presentedtoken against a single configured value (
check_bearer(presented, &Option<String>)) — there is noper-client bearer. Placing that bearer inside the gal tenant container means a compromise of that
container exposes access to the entire pooled key set, not just Gallaher's slice. Note that
constructionbot currently sends no auth header to sage at all, so this introduces a new secret with
fleet-wide blast radius into a client tenant.
Not a blocker — the operator has accepted the interim — but it should be a recorded decision rather
than a side effect. Bound it:
secret/gal/synthetic/credentials, fieldvalue), never inconfig.toml, git, or logs
A per-client bearer in synthshim would remove the tradeoff entirely, but that is a change in
symbiotrip/synthshim, out of scope for this repo.Remaining open questions (4 and 5 stand)
syn:large:visionaccepts image input. RFI attachments go throughpdftotexttoday.Sending page images for markup-heavy RFIs is plausibly a real accuracy win — Jake's misrouted RFI
involved a marked-up drawing — but it is a bigger change than a model swap. Keep it a separate ticket.
per-extraction cost during the calibration re-run required by the acceptance criteria.
Reference
symbiotrip/stp-infra→docs/runbook-add-synthshim-key.mdplatform/synthetic-shim.nix· Shim source:symbiotrip/synthshim· Spec 100