Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5b1ede5
fix(search): skip rephrase for first/single-turn chat questions
rajivml Jun 29, 2026
5a94a9c
feat(search): auto-routed one-shot Search tab (assistant routing)
rajivml Jun 29, 2026
4113aa9
feat(search): Sonnet router + @mention picker, refreshed Search UI, p…
rajivml Jun 30, 2026
c2c23e9
fix(search): soften HISTORY_QUERY_REPHRASE on this branch (match prod…
rajivml Jun 30, 2026
726564b
k8s(prod): bump backend=vha-206, web=vha-104 (one-shot Search feature…
rajivml Jun 30, 2026
f976a8e
feat(search): deterministic keyword pre-route for the auto-router
rajivml Jun 30, 2026
5a77590
k8s(prod): bump backend=vha-207, web=vha-105 (keyword pre-route)
rajivml Jun 30, 2026
16031d2
feat(auto-search): fuzzy+quoted keyword routing, gated LLM intent tie…
rajivml Jul 2, 2026
4c1c0e6
k8s(prod): web readiness+startup probes (zero-downtime roll); bump ba…
rajivml Jul 2, 2026
4031345
feat(auto-search): keyword priority ("always wins") + quality-aware r…
rajivml Jul 2, 2026
6e6bcd8
k8s(prod): bump backend=vha-209 web=vha-107 (keyword priority + quali…
rajivml Jul 2, 2026
e3c92cb
feat(search): side-by-side compare answers + @assistant typewriter hints
rajivml Jul 4, 2026
f3bf7a2
k8s(prod): bump backend=vha-210 web=vha-108 (search compare + @assist…
rajivml Jul 4, 2026
605f7ec
feat(chat): assistant-by-name URL param, referral logging, tool guard…
rajivml Jul 5, 2026
a14ab05
k8s(prod): bump backend=vha-211 web=vha-109 (referral logging + assis…
rajivml Jul 5, 2026
eb6a8cc
feat(ui): deliberate light theme + Slack /personas display-name & ACL…
rajivml Jul 5, 2026
539bcbe
k8s(prod): bump backend=vha-212 web=vha-110 (light theme + /personas …
rajivml Jul 5, 2026
f7e3e4a
fix(ui): color-scheme: only light — stop Chrome auto-dark force-inver…
rajivml Jul 5, 2026
a798cad
k8s(prod): bump web=vha-111 (color-scheme: only light)
rajivml Jul 5, 2026
ee4c811
feat(auth): preserve deep-link (?assistant=…) across SSO login, open-…
rajivml Jul 5, 2026
0b33b5e
k8s(prod): bump web=vha-112 (SSO next-preservation for deep links)
rajivml Jul 5, 2026
4a95c42
feat(slackbot): SME verification of answers (opt-in per channel, Slac…
rajivml Jul 7, 2026
724b553
k8s(prod): bump backend=vha-213 web=vha-113 (SME verification)
rajivml Jul 7, 2026
7d822f5
fix(slackbot): persist SME config — add fields to request-body whitel…
rajivml Jul 7, 2026
c80a17d
k8s(prod): bump web=vha-114 (persist SME config fields)
rajivml Jul 7, 2026
5d723d7
feat(web): make Darwin an installable PWA
rajivml Jul 10, 2026
8d8212e
feat(search): kNN-over-Slack assistant router + HighSpot/Docs compare…
rajivml Jul 16, 2026
49a7db5
feat(slackbot): 'Awaiting SME Review' label + comma-separated SME groups
rajivml Jul 16, 2026
a346aa6
docs: fix mermaid diagram colors in dark mode
rajivml Jul 16, 2026
6e3a291
chore(assistants): drop dead routing-instructions/intents fields from…
rajivml Jul 16, 2026
88bc8c1
chore(routing): fully strip routing_instructions/routing_intents from…
rajivml Jul 16, 2026
af7aaea
feat(routing): per-assistant 'Include in auto-routing' flag
rajivml Jul 16, 2026
f41c888
k8s(prod): bump backend=vha-214 web=vha-115 (kNN router, source tab, …
rajivml Jul 16, 2026
a7a1e33
feat(assistants): inline auto-routing toggle in the assistants list
rajivml Jul 16, 2026
a6878c8
feat(search): generate compare answers only on tab click
rajivml Jul 16, 2026
a1f195a
k8s(prod): bump backend=vha-215 web=vha-116 (inline auto-routing togg…
rajivml Jul 16, 2026
12b21fd
feat(routing): configurable min-votes threshold for recommendations
rajivml Jul 16, 2026
0372c03
k8s(prod): bump backend=vha-216 (min-votes recommendation threshold)
rajivml Jul 16, 2026
3bc45fb
config(prod): SLACK_KNN_ROUTER_TOP_K=25 (widen kNN neighbor pool)
rajivml Jul 16, 2026
8667a39
chore(backend): ignore missing google.* imports in mypy
rajivml Jul 16, 2026
da56ce1
style: apply black / prettier / ruff / import-ordering (pre-commit)
rajivml Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,18 @@ any connector type by introspecting `credential_json` keys.
Backend just uses the existing `PATCH /api/manage/admin/connector/{id}`
with `disabled: bool` flipped — no special bulk endpoint needed.

### Populate a local env from prod (Vespa + assistants/doc-sets)

To test search / assistants / the auto-router locally against realistic
data, `backend/scripts/clone_prod_to_local.py` copies the **latest N
docs per source** from prod Vespa (with embeddings / ACLs / doc-set
membership) plus the personas, prompts, and document sets from the prod
DB into your local Vespa + Postgres. Two phases — `export` runs inside a
prod pod, `import` runs locally — connected by a `kubectl cp`'d bundle.
Requires the **same embedding model** locally (the script enforces it and
aborts on mismatch). Full steps + caveats:
[`docs/clone-prod-to-local.md`](./docs/clone-prod-to-local.md).

---

## Conventions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""persona is_router_candidate

Revision ID: 31f30b318163
Revises: a7b8c9d0e1f2
Create Date: 2026-07-16 00:00:00.000000

Adds `is_router_candidate` to persona so an admin can exclude an assistant from
the auto-routed Search tab (both the keyword route and the kNN fallback) while
keeping it manually selectable. NOT NULL with server_default true, so existing
rows keep participating in routing (no behavior change on upgrade).
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "31f30b318163"
down_revision = "a7b8c9d0e1f2"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.add_column(
"persona",
sa.Column(
"is_router_candidate",
sa.Boolean(),
nullable=False,
server_default="true",
),
)


def downgrade() -> None:
op.drop_column("persona", "is_router_candidate")
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""chat_feedback SME verification

Revision ID: a7b8c9d0e1f2
Revises: f3a4b5c6d7e8
Create Date: 2026-07-07 00:00:00.000000

Adds sme_verified_by / sme_verified_at to chat_feedback so the Slack "Verified by
an SME" flow can record who verified an answer and when (extends the existing
feedback table rather than adding a new one).
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = "a7b8c9d0e1f2"
down_revision = "f3a4b5c6d7e8"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.add_column(
"chat_feedback",
sa.Column("sme_verified_by", sa.String(), nullable=True),
)
op.add_column(
"chat_feedback",
sa.Column("sme_verified_at", sa.DateTime(timezone=True), nullable=True),
)


def downgrade() -> None:
op.drop_column("chat_feedback", "sme_verified_at")
op.drop_column("chat_feedback", "sme_verified_by")
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""persona: add routing_instructions (router-only metadata)

Adds persona.routing_instructions — admin-editable free-text guidance read ONLY
by the auto-routed Search tab's assistant router (which assistant to pick for a
question). It is NEVER rendered in the user-facing UI; `description` stays the
short human-facing label. Nullable with no backfill — the router falls back to
`description` when this is blank, so existing assistants route on what they have
today until an admin fills this in. See db/models.py::Persona.routing_instructions
and secondary_llm_flows/assistant_router.

Revision ID: c0d1e2f3a4b5
Revises: b9c0d1e2f3a4
Create Date: 2026-06-29

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "c0d1e2f3a4b5"
down_revision = "b9c0d1e2f3a4"
branch_labels: None = None
depends_on: None = None


def upgrade() -> None:
op.add_column(
"persona",
sa.Column("routing_instructions", sa.Text(), nullable=True),
)


def downgrade() -> None:
op.drop_column("persona", "routing_instructions")
34 changes: 34 additions & 0 deletions backend/alembic/versions/d1e2f3a4b5c6_persona_routing_keywords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""persona: add routing_keywords (deterministic keyword pre-route)

Adds persona.routing_keywords — admin-editable, comma-separated phrases that
deterministically route a question to this assistant (case-insensitive substring
match) BEFORE the LLM router runs. Nullable, no backfill; blank means no keyword
override and the LLM router decides as before. Additive — existing routing is
unchanged when no keyword matches. See db/models.py::Persona.routing_keywords and
secondary_llm_flows/assistant_router.keyword_route.

Revision ID: d1e2f3a4b5c6
Revises: c0d1e2f3a4b5
Create Date: 2026-06-30

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "d1e2f3a4b5c6"
down_revision = "c0d1e2f3a4b5"
branch_labels: None = None
depends_on: None = None


def upgrade() -> None:
op.add_column(
"persona",
sa.Column("routing_keywords", sa.Text(), nullable=True),
)


def downgrade() -> None:
op.drop_column("persona", "routing_keywords")
35 changes: 35 additions & 0 deletions backend/alembic/versions/e2f3a4b5c6d7_persona_routing_intents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""persona: add routing_intents (semantic intent pre-route)

Adds persona.routing_intents — admin-editable, NEWLINE-separated natural-language
intent phrases (one per line). The auto-router embeds them and routes a question
to this assistant when its nearest exemplar clears a similarity gate, BETWEEN the
keyword pre-route and the LLM router. Nullable, no backfill; blank means no
semantic override and routing is unchanged. Stored in Postgres only (never in
Vespa); embeddings are computed on demand and cached per-assistant. See
db/models.py::Persona.routing_intents and assistant_router.intent_route.

Revision ID: e2f3a4b5c6d7
Revises: d1e2f3a4b5c6
Create Date: 2026-07-01

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "e2f3a4b5c6d7"
down_revision = "d1e2f3a4b5c6"
branch_labels: None = None
depends_on: None = None


def upgrade() -> None:
op.add_column(
"persona",
sa.Column("routing_intents", sa.Text(), nullable=True),
)


def downgrade() -> None:
op.drop_column("persona", "routing_intents")
55 changes: 55 additions & 0 deletions backend/alembic/versions/f3a4b5c6d7e8_chat_referral.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""chat_referral

Revision ID: f3a4b5c6d7e8
Revises: e2f3a4b5c6d7
Create Date: 2026-07-05 00:00:00.000000

Adds the chat_referral table: one row per landing on the chat UI from an external
referral (e.g. a per-channel Slack "Ask Darwin" workflow), so inbound traffic can
be measured by source / channel / assistant with plain SQL.
"""
from alembic import op
import sqlalchemy as sa
import fastapi_users_db_sqlalchemy

# revision identifiers, used by Alembic.
revision = "f3a4b5c6d7e8"
down_revision = "e2f3a4b5c6d7"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_table(
"chat_referral",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("utm_source", sa.String(), nullable=False),
sa.Column("utm_medium", sa.String(), nullable=True),
sa.Column("utm_campaign", sa.String(), nullable=True),
sa.Column("utm_channel", sa.String(), nullable=True),
sa.Column("assistant_name", sa.String(), nullable=True),
sa.Column(
"user_id",
fastapi_users_db_sqlalchemy.generics.GUID(),
nullable=True,
),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(["user_id"], ["user.id"]),
sa.PrimaryKeyConstraint("id"),
)
# Traffic queries filter/group by source and time.
op.create_index(
"ix_chat_referral_source_created",
"chat_referral",
["utm_source", "created_at"],
)


def downgrade() -> None:
op.drop_index("ix_chat_referral_source_created", table_name="chat_referral")
op.drop_table("chat_referral")
Loading
Loading