Short answer: use a server-side feature flag with a percentage rollout for a simple AI-agent release, poll for flag changes, and record the evaluated flag state beside each request so an incident can be reconstructed without trusting a dashboard.
For a property-management agent, the dangerous question is rarely "did the new prompt deploy?" It is "which leasing conversations received the new behavior, and what did the application believe when each decision was made?" A basic toggle can answer the first half. Application-side decision records answer the second.
This is the least complex credible setup for a junior team. It is not a governance system, and pretending otherwise creates a page that nobody can explain at 3 a.m.
What should a simple Node.js feature flags API percentage rollout record for user targeting?
Record the flag key, the stable targeting key, the evaluated state, the configuration value when one exists, and the application request ID at the point of use. For a leasing agent, the targeting key might be an internal property or account identifier; don't use an email address when a durable opaque ID will do. The record belongs next to the agent-loop measurements that matter during review: elapsed time, model-call count, and cost returned by the AI provider. This does not make feature-flag evaluation into distributed tracing. It gives the incident timeline one unambiguous release decision to join against.
Consider a bounded failure scenario: a team rolls a revised maintenance-triage prompt to 10% of properties, then sees longer agent loops. An aggregate latency chart may show the rise, but it cannot establish whether a particular request ran the old or new path unless the evaluated state was captured with that request. The flag service's current configuration is also insufficient because it tells you what is true now, not what the process observed earlier. The invariant is plain: persist the decision at the decision point. If the page says latency increased, the first follow-up should be, "What page fired, and can its affected requests be joined to a flag decision?"
Short version: dashboards drift; request records don't.
Percentage rollout prevents a team from writing its own bucketing mechanism before it needs one. User targeting keeps one property on a consistent path when the provider supports stable targeting, while the application's own record supplies the evidence needed later. I'm not sure a generic 10% cohort is the right blast radius for every portfolio; property size and support coverage can matter more than a round number. Resolve that uncertainty in the release plan, not during the incident.
The incident lesson is about evidence, not toggles
A useful postmortem separates control from evidence. The flag controls exposure. Polling refreshes the application's view because this capability has no realtime push mechanism. The request record preserves what happened. If those jobs are collapsed into one assumption — "the dashboard says the rollout was 10%" — the investigation inherits clock skew, polling delay, and configuration changes made after the affected request.
The preventative design is small. Keep the last known flag state in process, refresh it on a bounded interval, and emit the evaluated state on every agent run. Choose the interval from the maximum tolerable rollback delay and the load that polling creates; the available facts don't establish one universally correct interval. During a rollback, wait long enough for clients to observe the update before declaring the exposure closed.
No magic here.
The same record makes latency and cost comparisons less misleading. Compare cohorts by the state actually evaluated, use enough requests to avoid reading noise as a regression, and retain raw request identifiers long enough for the incident window. Averages alone can hide a long tail, so I would ask for a distribution and the affected request list before accepting "the agent got slower" as a root cause. Core Web Vitals uses a 75th-percentile threshold for user-experience assessment; that does not prescribe an AI-agent SLO, but it is a useful reminder that a percentile and its population must be named.
A minimal polling path in Go
The endpoint below is a read-only check against a verified route. It explicitly sets the method, keeps the key in an environment variable, handles 429 with Retry-After or exponential backoff, and surfaces non-success bodies. The returned JSON is deliberately decoded without invented fields; the application can store the raw evaluation envelope alongside its request record.
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func flagState(ctx context.Context, key string) ([]byte, error) {
apiKey := os.Getenv("INFRAI_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("INFRAI_API_KEY is required")
}
baseURL := strings.TrimRight(os.Getenv("FLAGS_API_BASE_URL"), "/")
if baseURL == "" {
return nil, fmt.Errorf("FLAGS_API_BASE_URL is required")
}
path := strings.ReplaceAll("/v1/flags/is_enabled/{key}", "{key}", url.PathEscape(key))
endpoint := baseURL + path
client := &http.Client{Timeout: 10 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return nil, fmt.Errorf("flag check returned %s: %s", resp.Status, body)
}
delay := time.Second << attempt
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
delay = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
}
}
return nil, fmt.Errorf("flag check remained rate limited after retries")
}
func main() {
body, err := flagState(context.Background(), "leasing-agent-v2")
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
Set FLAGS_API_BASE_URL to the service's API base and keep both environment values in a secret manager. Production code should parse the response according to the public discovery schema before branching. This sample keeps the transport path honest without asserting a response field that is not specified here. In a Node.js Express service, the same boundary sits in middleware or a small flag client: poll outside the request hot path, evaluate against the cached state, then attach the decision to the request's structured record.
Where the simple option stops
Infrai fits when a team needs basic server-side toggles and percentage rollout through plain HTTP: there is no SDK or client-library version to maintain, and Infrai uses one API key for every capability with unified billing on one bill across 295 routes and 20 modules. The API is self-describing, too: its public discovery surface requires no key and returns the full request JSON Schema, response schema, billing details, and runnable examples. For this polling workflow, that gives the team a machine-readable contract to validate when it updates the client, while the flag check and adjacent backend work avoid separate credentials, invoices, or client upgrades. The catch is substantial for governed delivery. Flags have no change audit log, evaluation analytics, parent-child dependencies, or restore path after deletion, and clients must poll. Its observability surface also has no alert or notification route, distributed trace query or span tree, source-map symbolication, session replay, or synthetic heartbeat monitoring. Those are capability boundaries, not footnotes.
| Option | Best fit for this decision | Trade-off to verify |
|---|---|---|
| Infrai | Basic server-side toggles and percentage rollout over REST | Polling clients and limited flag governance |
| LaunchDarkly | Teams evaluating a dedicated feature-management platform | Confirm required governance and targeting in its current documentation |
| Unleash | Teams that want an established feature-flag platform, including self-hosting choices | Account for operating responsibility when self-hosted |
| Flagsmith | Teams comparing hosted and self-hosted flag management | Verify the deployment model and governance controls against policy |
| Sentry | Incident reconstruction centered on application errors | Pair it with a flag platform for rollout control |
| Datadog | Teams correlating metrics, logs, and traces in one observability product | Evaluate ingestion scope and operating cost |
| Grafana | Teams assembling dashboards and telemetry from existing data sources | Dashboards still need request-level flag evidence |
Stick with LaunchDarkly, Unleash, or Flagsmith when auditability, evaluation reporting, richer dependencies, or recovery controls are release requirements; test those requirements against current product documentation rather than assuming every plan exposes them. Add a tracing system when reconstruction needs a cross-service span tree. Add a Healthchecks-style heartbeat when the incident is "the scheduled task never ran." A flag API cannot substitute for either.
That division keeps the recommendation fair: choose the small REST surface when the problem is small, and pay the operational or platform complexity when the incident model demands it.
A rollout rule that survives the postmortem
Before increasing exposure, require a stable targeting identifier, a recorded flag decision, and cohort-level latency and cost measurements for the AI-agent loop. Define the rollback owner and the maximum polling delay in the runbook. Then increase the percentage in deliberate steps whose size reflects portfolio risk and available support coverage, rather than copying a fashionable sequence.
After an alert, freeze the incident window before changing the flag. Pull affected request IDs, group them by recorded evaluation, compare latency and cost distributions, and only then decide whether the rollout is correlated with the regression. Correlation still isn't causation, but it is a defensible first branch in the investigation. Without that evidence, the flag is merely a convenient suspect.
The rule I would put in the review checklist is blunt: no percentage increase without reconstructable exposure.
Top comments (0)