TL;DR
This guide adds one controlled prompt or model experiment to a Node.js and TypeScript LLM workflow. GrowthBook selects a reviewed version key, while the repository owns prompt text, provider credentials, authorization, timeouts, privacy controls, and fallback behavior. Assignment stays stable, exposure occurs immediately before a real model call, and outcome events measure quality, reliability, latency, and cost without sending raw prompts or completions to GrowthBook. The release moves from an unchanged control, to an internal canary, to a bounded production experiment, and then to a deliberate ship or rollback decision. A treatment that cannot pass offline evaluation, privacy review, deterministic tests, failure injection, and a documented stopping rule does not advance to production traffic. This guide is optimized for AI coding agents, and it is recommended that you hand it off to your agent of choice for implementation.Guide map
Give this guide to your coding agent
Copy this page’s URL and the prompt below into a coding agent that can inspect your repository. ReplaceREPLACE_WITH_REPOSITORY with the repository path or name and REPLACE_WITH_GUIDE_URL with this page’s public URL. Start in a branch, worktree, or disposable clone, and use offline fixtures or approved synthetic inputs for the first pass.
Task
Add a controlled production experiment to an existing Node.js/TypeScript LLM workflow. A stable user receives either the current prompt/model or one treatment. GrowthBook records exposure only when the application actually calls the model. The application records quality, reliability, latency, and usage outcomes without sending prompts, completions, email addresses, or other sensitive text to GrowthBook. This guide is written so that a coding agent can implement the change in one pass. It includes the application boundary, complete reference files, GrowthBook configuration, event contract, tests, release sequence, failure branches, rollback procedure, and the criteria for deleting the experiment code. The reference workflow is a support-answer generator, but the architecture also applies to summarization, extraction, classification, search reranking, recommendations, and agent steps.Use this guide when
- An LLM-backed capability already has a known-good production behavior.
- You want to compare one prompt, model, tool policy, or decoding change against that behavior.
- You can identify a stable randomization unit such as
user_idoraccount_id. - You can observe at least one outcome that represents user value, not just model activity.
- You need a kill switch and a defensible record of who received which treatment.
Do not start a production experiment when
- The treatment has not passed deterministic tests and an offline evaluation set.
- The treatment can take irreversible actions without an independent permission boundary.
- You have no stable identifier. Randomizing every request creates cross-contamination and usually answers the wrong question.
- The only proposed success metric is model-graded quality from the same model family being tested.
- Logging a useful outcome would violate your privacy, security, or retention policy.
- The treatment changes several things at once and you need to know which one caused the result.
End state
You are done when all of the following are true:- The control path is byte-for-byte or behaviorally equivalent to the pre-experiment implementation.
- Assignment is stable for the chosen unit and is evaluated once per workflow run.
- Exposure is emitted only immediately before a real provider call.
- Outcome events use the same identifier as assignment.
- Raw prompts, completions, credentials, and direct personal identifiers are absent from GrowthBook attributes and event properties.
- A config-fetch failure returns the control behavior.
- A treatment-provider failure either fails closed to a human or performs a clearly labeled fallback; it never silently counts the fallback as treatment success.
- Tests prove control, treatment, exposure, outcome, and failure behavior.
- The experiment has a written hypothesis, primary metric, guardrails, minimum runtime, and stopping rule before traffic begins.
- An on-call engineer can disable the treatment without a deploy.
What this guide changes
The implementation adds these files to a typical service:Architecture and trust boundaries
GrowthBook decides which version key applies. Your repository owns the prompt text, model allowlist, provider credentials, input validation, authorization, timeout, and fallback behavior. Do not put an unrestricted system prompt, API key, or provider request body into a remotely editable flag. This distinction is important. A remote flag is an operational control plane. It should select reviewed code, not become an unreviewed code-delivery channel.Decide what one experimental unit means
Pick this before touching code. The identifier used for assignment, exposure, and metrics must describe the same unit.
The reference implementation randomizes by
user_id. For a multi-user B2B workflow, change both the SDK attribute and all outcome events to account_id, mark that attribute as an identifier in GrowthBook, and analyze at the account level.
Never create a random ID inside runSupportAnswer. An identifier generated at evaluation time is stable only for that function call, not for the experiment.
Write the experiment contract first
Put this in the experiment description or the team’s experiment record before launch:Stage 0: establish a known-good control
Before GrowthBook is involved:- Capture at least 50 to 200 representative, redacted inputs from production or construct a reviewed evaluation set.
- Record the current response, tool calls, structured output validity, latency, token counts, and human quality labels.
- Add adversarial and policy-sensitive cases.
- Define hard failures: invalid JSON, prohibited action, missing citation, fabricated identifier, timeout, or escalation omission.
- Run control and treatment through the same evaluator.
- Reject a treatment with a hard-failure regression even if its average score improves.
Stage 1: install the SDK
Install a pinned major version and record the resolved version in your lockfile:1.7.0. If the installed version differs, compare the current Node.js SDK documentation and changelog before copying the code.
Add server-only configuration:
.env.example
Stage 2: create a version registry
Keep the current and candidate behavior explicit and reviewable.src/llm/prompt-variants.ts
"model-b" as a third registry entry but run it in a separate experiment from the prompt change. Otherwise the result only tells you that the bundle changed.
Stage 3: define a provider-neutral boundary
This adapter keeps vendor response objects out of experiment code and makes tests deterministic.src/llm/provider.ts
InvalidLlmOutputError for malformed JSON or a schema failure; preserve ordinary transport/provider failures as other errors. Do not put raw prompts or responses in the error message. The workflow also validates the normalized result at its boundary, so a faulty adapter cannot silently record malformed output as a success. Shape validation does not establish factual accuracy or policy compliance; retain the separate evaluation and policy checks.
Stage 4: initialize one multi-user GrowthBook client
Create one long-livedGrowthBookClient per process, initialize it during startup, and create a user-scoped instance for each workflow. Do not create a new network-fetching client for every request.
Create a polling helper for the pinned SDK. Version 1.7.0 supports refreshFeatures, but not the newer init({ pollingInterval }) option.
src/experimentation/feature-refresh.ts
src/experimentation/growthbook.ts
initializeGrowthBook() once from the service startup path. The JavaScript SDK reports initialization failure in the returned result; a timeout does not need to throw. An unloaded feature evaluates to null, and getFeatureValue(..., "control") therefore supplies control.
Polling continues after an initial fetch failure and is stopped on shutdown. Publish-to-evaluation latency is up to the 60-second interval plus delivery time when the endpoint is healthy. A failed refresh retains the last payload, which may still select treatment; it does not automatically switch an already-running service to control. SDK 1.7.0 does not return a success receipt from refreshFeatures(), so monitor delivery and prove rollback propagation in staging. If that window is too slow, use the Node.js streaming setup and verify it before launch. Retain a deployment-level rollback to the known-good control for a delivery outage.
If your GrowthBook Cloud data region is eu-west-1, use https://eu-west-1.gb-ingest.com. A wrong ingestor region can make evaluation work while event data silently lands in the wrong place or is dropped.
Stage 5: define a low-cardinality event contract
The events below intentionally omit question text, answer text, prompt text, user email, customer name, and provider credentials.src/analytics/llm-events.ts
estimatedCostUsd; do not hard-code provider prices in this guide or in a long-lived analytics module. Prices change. Version the pricing table and record which pricing version produced the estimate.
treatment is useful for operational debugging, but GrowthBook analysis joins outcomes to the canonical exposure event. Do not rebuild assignment by grouping this property.
Stage 6: put evaluation immediately before use
This is the critical implementation. Evaluation happens after authorization and input validation, but before the provider call. The automatic experiment exposure therefore corresponds to an activated workflow, not a page view or a request that was rejected early. First add a deadline wrapper. Aborting requests cooperative cancellation; racing against a rejecting timer also bounds the caller’s wait when an adapter ignores the signal. The elapsed-time check rejects a result that arrives after the deadline even if the timer callback was delayed.src/llm/provider-deadline.ts
src/llm/run-support-answer.ts
support_llm_fallback event with from_treatment, to_treatment, and reason; count it as a guardrail failure for the assigned treatment.
Stage 7: add deterministic tests
The test usesinitSync with an explicit payload. It performs no network calls and proves both feature branches.
tests/run-support-answer.test.ts
Stage 8: configure GrowthBook
Create or select the SDK Connection
- In GrowthBook, open SDK Connections.
- Create a server-side JavaScript/Node connection for the correct project and environments.
- Copy the API host and client key into your secret/configuration system.
- Under Attributes, ensure
user_idexists and is marked as an identifier. - Keep attributes deliberately small:
user_id,surface, and non-sensitive targeting fields only.
Create the feature
Create a string feature with:
Publish this control-only feature first. Deploy the application code while every user remains on control. This separates “code deployed” from “experiment started” and proves the new plumbing without changing behavior.
Verify control-only production
Run one internal request, then check:- service logs show a successful GrowthBook initialization or a known safe fallback;
- provider request uses the control registry entry;
- GrowthBook SQL Explorer contains
Feature Evaluated,Experiment Viewedonly after the experiment rule exists, and the custom LLM events; - no prompt or answer text appears in
propertiesorattributes; user_idis populated for exposure and outcome rows.
experiment_views table. Verify identifiers and variation IDs there as well:
Create metrics
Using the Managed Warehouse Events fact table, create:
Set metric windows to reflect when the outcome can realistically occur. An immediate structured-output failure may use minutes; “ticket reopened” may need days. Do not close an experiment before late outcomes have had time to arrive.
If you use your own warehouse, implement the same semantic contract in its event table and connect GrowthBook with a dedicated read-only user. Pipeline Mode is the exception: it requires write access to a dedicated schema for temporary analysis tables.
Create and link the experiment
- Go to Experiments, choose Add → Create New Experiment.
- Name it
Support answer prompt v2. - Set the hypothesis from the prewritten contract.
- Set assignment attribute to
user_id. - Add the goal and guardrails above.
- Link
support-llm-treatmentas a feature-flag experiment. - Configure variations
controlandprompt-v2with a 50/50 relative split. - Start at low overall exposure for the canary; change overall exposure, not the variation weights, when ramping.
- Publish the feature changes.
Stage 9: release from 0 to 1 to 100
0: dark deployment
- Both registry entries are deployed.
- The feature is forced to
control. - Event flow and privacy are verified.
- Offline evaluation passed.
- The kill-switch owner is named.
1: internal canary
Use a targeting rule above the experiment rule for employees or test accounts and forceprompt-v2. This validates real provider credentials, latency, parsing, and user interface behavior but is not randomized evidence.
Then start the randomized experiment at low overall coverage, for example 5%, while retaining a 50/50 split inside the experiment. Watch absolute error, timeout, policy, escalation, and cost counts. At low coverage, statistical outcome metrics will be noisy; operational guardrails are the decision signal.
10 to 50: controlled ramp
Increase overall exposure only after:- there are no hard policy failures;
- event joins are healthy;
- both variations have traffic;
- there is no Sample Ratio Mismatch warning;
- provider capacity and rate limits have headroom;
- latency and cost distributions are within the predeclared bounds.
100: full experiment population
“100% experiment coverage” does not mean “100% treatment.” It means all eligible users enter the experiment and are divided by the configured variation weights. Continue until the minimum runtime and precision criteria are satisfied. Do not repeatedly stop when the result looks good. GrowthBook supports experiment health checks such as Sample Ratio Mismatch, frequentist/Bayesian analysis, and multiple-testing controls; some advanced statistical features depend on the current plan. Use the capabilities available to your organization and keep the decision rule fixed.Outcome instrumentation after the model call
Model completion is not resolution. Record downstream outcomes where they actually become known:- Log
support_answer_resolvedwhen the product’s resolution criterion is satisfied. - Log
support_answer_reopenedwhen a resolved case reopens inside the metric window. - Log
support_answer_escalatedwhen a user or policy routes to a human. - Log
support_answer_policy_violationfrom an independent reviewed classifier or human adjudication path.
logUserOutcome. If the outcome arrives through an asynchronous worker, pass the non-sensitive stable identifier in the job payload or look it up from internal storage. Do not pass the assigned treatment; let exposure data be the source of truth.
Privacy and security checklist
- Treat all SDK attributes and event properties as analytics data that can be queried later.
- Use opaque internal IDs. Do not use email addresses as identifiers.
- Do not send prompt text, retrieved documents, completions, tool arguments, secrets, or provider request bodies.
- Bucket continuous/high-cardinality debug fields before analytics, or keep them in your normal protected observability system.
- Apply your retention and deletion policy to the event pipeline.
- Give GrowthBook warehouse access the least privilege required.
- Keep the GrowthBook control plane private when self-hosting; expose only the feature-delivery layer needed by SDKs.
- Validate all remote string values against a local allowlist.
- Preserve the application’s authorization checks outside the experimental branch.
Failure matrix
Rollback
With healthy feature delivery, roll back through GrowthBook without a deploy:- Open
support-llm-treatment. - Stop or disable the experiment rule.
- Force production to
control. - Publish.
- Allow for the 60-second polling interval plus fetch time. Verify fresh requests on each serving process receive
controland the provider uses the control model/prompt; publishing alone is not a rollback receipt. - Monitor until in-flight treatment calls drain.
Decide, ship, and clean up
At the predeclared review point:- Resolve SRM, multiple-exposure, pre-exposure bias, missing-data, and late-outcome warnings before interpreting lift.
- Review the primary metric and confidence/credible interval, not only the point estimate.
- Check every guardrail and absolute event count.
- Segment only to diagnose or generate a new hypothesis; do not manufacture a win from many slices.
- Record one decision: ship, rollback, or run a new experiment.
- Force
prompt-v2for all eligible production traffic. - Observe for one additional operational window.
- Make the treatment registry entry the new local control.
- Delete the feature evaluation and dead prompt/model entry in a normal code review.
- Archive the experiment with the decision and links to the implementation and rollback.
- Delete no-longer-needed analytics properties only after downstream dashboards have migrated.
DIY versus GrowthBook
A small team can implement deterministic bucketing with a hash function and write exposure rows to a database. For one short-lived experiment, that may be rational. The difficult part is the system surrounding the hash:- keeping assignment stable across services, login transitions, retries, and phases;
- separating feature delivery from exposure and activation;
- joining outcomes on the correct identifier and time window;
- detecting Sample Ratio Mismatch and multiple exposures;
- controlling access to production changes;
- retaining an audit trail and a fast rollback path;
- computing repeatable statistical results with multiple metrics;
- retiring stale experiment code and flags;
- supporting dozens of concurrent experiments without every team inventing conventions.
Definition of done for an implementing agent
Return these receipts, not merely “implemented”:Troubleshooting commands
Confirm installed SDK version:Source map and freshness contract
This guide depends on these current primary sources:- Node.js SDK for
GrowthBookClient, scoped instances, initialization, caching, and tracking behavior. - Managed Warehouse for the tracking plugin, ingestor regions, identifier mapping, SQL Explorer, and event schema.
- Feature flag experiments for assignment, exposure, weights, namespaces, and experiment rules.
- Experiment configuration for setup, metrics, phases, and activation guidance.
- Experiment results for SRM, multiple exposures, pre-exposure bias, and interpretation.
- GrowthBook pricing for current plan boundaries. Verify plan-specific functionality at implementation time.
- GrowthBook source at
e44a15affor the SDK and documentation behavior tested here.

