Skip to content

Repository files navigation

BridgeAI

A requirements agent that lives in Slack. It interviews each stakeholder privately, finds the contradictions between them, and answers developers' questions only from requirements a named human confirmed — or asks the person who owns the decision and brings the answer back to the developer's own thread.

There is no dashboard. Slack is the product. One read-only web page renders the final plan.

Why it can't be a chatbox

The agent starts the conversations. It DMs four people who never messaged it, adapts each interview to what that person just said, posts a contradiction into the team channel where only the two people who disagree can settle it, and carries an answer from a stakeholder's DM back into a thread somewhere else. None of that is reproducible in a single chat window.

The two things to watch

Hero 1 — the contradiction nobody knew about. Operations says a certificate is issued automatically on completion. Finance says it needs payment confirmation. Neither ever mentioned the other's condition, and the synthesizer is forbidden from merging them into one rule. It posts a conflict card in the channel; only the two people tagged on it can resolve it; resolving it creates the confirmed requirement, with an Approval row recording who decided and when.

Hero 2 — "I don't know, and here is who does." A developer asks @BridgeAI can an admin revoke a certificate after download?. No confirmed requirement states it, so the agent refuses to answer, names Operations as the owner, DMs Anna one answerable question, and posts her answer back into the developer's original thread — now a confirmed requirement with her as the source.

The gaps are filled before the team sees the plan. Synthesis posts the conflict card to the channel immediately — two people disagreeing is the team's business — but holds the plan link and DMs every open question privately to the stakeholder who owns it, through the same Answer-button loop a developer's question uses. Each answer confirms that requirement in place, with them as the source. When the last one lands, the plan posts itself to the channel, exactly once. /bridgeai plan publishes early if you don't want to wait.

Needs discussion is a conversation, not a shrug. Pressing it opens a modal asking what has to be discussed; the owner's own words are stored on the approval record, shown on the plan page, and quoted back when BridgeAI asks them to agree the final wording for each item. Those items are held out of the plan until they do — an owner parking something is not a way for it to quietly disappear.

How trust is enforced (in code, not in the prompt)

  • The synthesis schema has no confirmed value. The model physically cannot confirm anything.
  • Requirement.status = 'confirmed' is only ever written in the same transaction as an Approval row. Three paths write one: a sign-off, a conflict resolution, a routed answer.
  • The model cites short aliases (A7, R2), never database ids, and every alias that comes back is validated. A requirement citing a source that does not exist is downgraded to an assumption, and the fake alias is kept and shown — on the plan page and on the channel message.
  • The Router writes its analysis before its answer. If any part of the question has no evidence, code trusts the analysis over the answer and routes to a human. An answer with an unverifiable citation is never posted.

Running it

Two processes, one repo, one database. No HTTP between them.

pnpm install
createdb bridgeai          # local Postgres
cp .env.example .env       # fill in the Slack, database and OpenAI values
pnpm db:push

pnpm bot                   # Bolt over Socket Mode — no public URL needed
pnpm dev                   # the plan page on http://localhost:3000

Slack app

Socket Mode on. Bot scopes commands, chat:write, im:write, users:read, users:read.email, app_mentions:read, channels:history, channels:read. App-level token with connections:write. Event subscription: app_mention only. Interactivity on. App Home Messages Tab on. Slash command /bridgeai. Invite the bot to the project channel/bridgeai new refuses to start otherwise.

Commands

/bridgeai new create a project and interview its stakeholders (new demo prefills the demo)
/bridgeai status who has answered, and what has been decided
/bridgeai plan open the drafted plan
/bridgeai synthesize draft the plan now, without waiting for everyone
/bridgeai ask re-send any open question that hasn't reached its owner (synthesis does this automatically)
/bridgeai ping check the bot, the database and the model
@BridgeAI … ask a question in the project channel

Scripts

pnpm seed:reset     # wipe every project (everything cascades)
pnpm seed:fill      # give the live project three complete interviews (Finance, Support, Engineering)
pnpm ai:check       # the demo-reliability tests, against the real model

seed:fill never triggers synthesis — synthesis has to post the conflict card, and a script has no Slack client. Run Anna's interview live, or /bridgeai synthesize.

Testing it in Slack

TESTING.md is the click-by-click runbook: pre-flight, the demo sequence in order, the exact answers Anna must give for the conflict to appear, and a symptom → cause troubleshooting table.

The four agents

Each is a narrow operation with a fixed input and a Zod-validated output schema. None of them knows Slack exists, and none touches the database — which is what makes pnpm ai:check possible without either. Every one writes structured analysis before its answer, and code checks the two agree.

Agent Thinks first Then answers
Interviewer every capability in the brief, what this role decides, what the brief never states 4–6 round-1 questions
Follow-up Interviewer a verdict per answer, the decisions still open done, or 1–3 questions quoting the person's own words
Synthesizer a model of the whole product and its coverage requirements, conflicts, open questions
Router each part of the question and the requirement that states it answered, or unknown + the owner

The interview is adaptive, not a form: round 1 is 4–6 questions, and every later question is generated from what that person just said, quoting it back ("Because you said …"), for up to three rounds. The Follow-up agent never sees another stakeholder's answers, so contradictions can only surface in public, on the conflict card.

Layout

bot/     Slack ↔ lib translation. No business logic. lib/ never imports @slack/*.
lib/     services + lib/ai/* (pure: context in, validated output out, no DB)
app/     one server-rendered route: /plan/[projectId]
scripts/ seed, seed data, ai:check

Configuration worth knowing

Variable Effect
MAX_ROUNDS 3 = round 1 + two follow-up rounds; 2 if you are short on time
FOLLOWUPS_ENABLED false turns the adaptive interview back into a form — don't
PLAN_PAGE_ENABLED false posts the plan as a Slack message instead of linking the page
APPROVALS_MODE minimal (default) is the one "Approve all my items" button per owner. full (per-requirement approvals) is cut item #1 in the plan and has no service contract, so the flag currently records the mode without changing behaviour.
OPENAI_BASE_URL set only for a sponsor-provided gateway
DEMO_SLACK_USER_IDS prefills the people pickers for /bridgeai new demo. Use the labelled form — Operations:U…,Finance:U…,Support:U…,Engineering:U… — so order cannot silently put the wrong person in a role. The bare positional form still works. Entries that don't name a role (a developer account, say) are ignored.

Notes from the build

  • temperature is not sent. The chosen model rejects it (400 Unsupported parameter). Each operation still declares its intended temperature, and lib/ai/client.ts documents how to restore the parameter for a model that accepts it.
  • Array bounds are validated in code, not in the schema. Structured Outputs strict mode does not honour minItems/maxItems, so sanitizeQuestions enforces 4–6 (and ≤ 3 for follow-ups) after parsing.
  • Blank evidence is not evidence. The Router sometimes returns "" or ".none" for a part it cannot support. lib/ai/answerDeveloperQuestion.ts accepts a value only if it is a real R<n> alias — anything else becomes null and forces routing.
  • An open_question is exempt from the citation downgrade. It carries category business_rule (the category the decision will have once it is made) and may legitimately cite nothing; downgrading it to an assumption would erase the gap the plan exists to show. An asserted rule with no valid source is still downgraded.
  • ai:check reports one result as KNOWN: the answered path occasionally routes "When should the certificate be generated?" to Operations instead of answering from a requirement about when a certificate may be issued. Loosening the prompt to force an answer would weaken exactly the evidence-strictness Hero 2 depends on, so it is reported and not counted as a failure.
  • pnpm 10 blocks dependency build scripts. @prisma/client's own postinstall is what generates the client, so a plain pnpm install left it ungenerated and Next failed with Can't resolve '@prisma/client'. Fixed two ways: onlyBuiltDependencies in pnpm-workspace.yaml approves Prisma's scripts, and a postinstall: prisma generate script in package.json regenerates it regardless.
  • Question has no basedOn column, so follow-up rounds keep each question's basedOn on Interview.analysis, keyed by position — the interview modal needs it for its hint.

What was built when

Prepared the night before, as the event rules allow for prompts and starter code: the Slack workspace and app configuration, the create-next-app scaffold and dependency install, the empty bridgeai database, and the four system prompts in lib/ai/prompts/.

Everything else — the Prisma schema, every service in lib/, every handler and Block Kit surface in bot/, the plan page, the seed data and the reliability tests — was written during the build window.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages