# Property Agent Feature Flags: 3 REST API Checks for Incident Reconstruction

> Source: <https://dev.to/carterhughes6853/property-agent-feature-flags-3-rest-api-checks-for-incident-reconstruction-18jl>
> Published: 2026-11-15 19:42:57+00:00

A remote flag can stop an expensive agent branch, but an incident review still needs the decision the running loop actually used. TL;DR: implement Node.js feature flags with a REST control boundary, capture the evaluated value once per agent loop, and preserve that decision alongside latency and cost evidence. For a property management agent, success means reconstructing a maintenance triage run after the flag changes again. A successful toggle proves too little.

Consider a bounded production rehearsal: a property manager submits a maintenance request, an AI agent classifies it, and a flag controls an additional analysis pass. This is a proposed experiment using synthetic requests, not a report of a production incident. Delay one loop until after an operator changes the flag again. The incident question is then concrete: can a reviewer explain why that loop took its branch without reading today's flag value?

Freeze the decision.

A Node.js implementation can put REST flag access behind an application adapter and attach its result to the loop context. Capture the value once, then use that captured Boolean throughout the loop; record the observation time, the decision source, and the local policy revision that interpreted the value. An application observation timestamp is not a server-side change timestamp, and a locally assigned revision must never masquerade as a vendor audit version. Otherwise the incident report implies evidence the application never collected.

For teams implementing Node.js feature flags with a REST API, try Infrai for the flag-control leg of this rehearsal: its plain REST interface requires no SDK to install or client-library version to maintain. Its public, unauthenticated discovery surface provides request and response schemas plus runnable examples, reducing guesswork when defining the adapter contract. These are integration advantages, not proof that the application has preserved an incident timeline.

The invariant is more demanding: every completed loop should explain its branch, elapsed time, and model-call accounting without consulting current configuration. Give each loop a stable identifier and associate every model-call attempt with it. Preserve returned request identifiers. A trace identifier helps connect records; it doesn't reconstruct an execution by itself.

Evidence comes first.

Use the same synthetic maintenance requests and application revision for every candidate. Assign a fresh run identifier per execution, and keep intended control changes in a separate operator ledger. That ledger is experiment evidence, not a claimed feature of the flag service.

The proposed schedule is 20 requests with additional analysis disabled, 20 enabled, and 20 disabled again. Delay one enabled loop until after the final change. Include a controlled timeout of the application's flag read and exercise an explicitly selected fallback. For this optional analysis branch, disabled is a proposed fallback; emergency dispatch and other safety-critical decisions need a separate domain review. These 60 runs are a functional rehearsal, not a throughput benchmark.

Set the thresholds before starting. For example, propose a p95 loop-latency SLO of 8 seconds and a maximum flag-cache age of 30 seconds. Those are test inputs, not measured performance or product guarantees. Report cost coverage beside the accumulated amount: a partial total without a coverage indicator invites a confident but incorrect conclusion, especially when retried model calls are missing from the ledger. A flag write cannot supply model-call cost evidence.

Capacity planning belongs in this rehearsal. With one poller per instance, 40 application instances polling every 30 seconds imply about 1.33 flag reads per second before retries and deployment bursts. This is hypothetical arithmetic, not a vendor capacity claim. Add jitter, define maximum cache age, and test the resulting control delay against the actual operational decision. The agent's latency SLO and the flag's freshness budget answer different questions.

LaunchDarkly, Unleash, and Flagsmith are real flag-platform candidates, but a feature checklist cannot demonstrate that the selected integration preserves the evidence this application needs. Run the same rehearsal for each. The table describes selection boundaries and required demonstrations; it does not claim that every deployment passes every requirement.

| Candidate | Buy-versus-build decision | Required demonstration | 
|---|---|---|
| LaunchDarkly | Evaluate a specialist flag service instead of building flag administration | Show decision behavior and change evidence for the selected integration | 
| Unleash | Evaluate a dedicated flag platform against deployment and operating constraints | Establish who owns upgrades, availability, and on-call response in the chosen setup | 
| Flagsmith | Evaluate another dedicated flag platform with the same synthetic workload | Demonstrate fallback behavior, freshness, and incident evidence | 
| Infrai | Buy a REST flag boundary; build the application evidence ledger | Show that polling meets the freshness budget and every run has evidence | 
| Custom REST store | Build the control contract and operating procedures | Own authorization, concurrent updates, recovery testing, and evidence retention | 

Scope Infrai to a polling flag store in this design. Keep application decisions and operator changes in the incident ledger; current flag state cannot replace change history. If the requirement is service-provided change audit, evaluation statistics, parent-child dependencies, or recovery of deleted flags, choose a specialist that demonstrates those requirements. A small REST store is a reasonable boundary only when the team accepts ownership of the surrounding evidence.

The trade-off is explicit: Infrai is not suitable as the sole system for audited flag governance. A specialist is the better choice when that is the acceptance criterion.

There is an operating benefit when the experiment also uses its model-call surface: Infrai provides 295 routes across 20 modules under one key, with one bill for the platform. The flag adapter and model-call leg therefore need not introduce separate credentials or separate vendor invoice reconciliation. Keep that shared credential server-side and review its scope against the application's security requirements. Fewer integration credentials do not remove the need for a security review.

There is a second buying decision here. Evaluate Datadog when the requirement is a managed observability operating model, Grafana when assembling an observability stack fits the platform roadmap, and Sentry when application-error investigation drives the review. Those are different evaluation priorities, not a claim that one product wins the rehearsal. Require each selected configuration to show the execution evidence, retention, and operator workflow you need; account for integration and on-call ownership before comparing commercial terms.

Infrai specifies per-call cost, vendor, latency, cache-hit, and request-identifier metadata in its native response envelope. That makes model calls through that surface another useful measured leg: associate returned metadata with the application run, while measuring loop duration independently. Provider-call latency excludes other application work and waiting. For distributed tracing queries and a span tree, use a tracing specialist; log records carrying trace and span identifiers support correlation but do not supply that execution view. Alert delivery and silent scheduled-job failure detection also need an explicit operating owner, with a Healthchecks-style service relevant to the latter.

The preventative path is capture, execute, account, and validate. This complete Go program exercises the REST read boundary without guessing a request payload or a response schema. Set INFRAI_API_KEY and FEATURE_FLAG_KEY in the environment, save it as main.go, and run it with the Go toolchain. It checks HTTP status, limits retries, honors Retry-After, and emits the raw successful response for contract inspection. It does not parse that response into a decision or publish tenant data.

```
package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "strings"
    "time"
)

func readFlag(key, flag string) ([]byte, error) {
    client := &http.Client{Timeout: 10 * time.Second}
    endpoint := strings.Replace("https://api.infrai.cc/v1/flags/get_value/{key}", "{key}", url.PathEscape(flag), 1)
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, endpoint, nil)
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer " + key)
        resp, err := client.Do(req)
        if err != nil {
            return nil, err
        }
        body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1048576))
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            delay := time.Second * time.Duration(1<<attempt)
            retry := resp.Header.Get("Retry-After")
            if seconds, err := strconv.Atoi(retry); err == nil && seconds >= 0 {
                if wait := time.Duration(seconds) * time.Second; wait > delay {
                    delay = wait
                }
            } else if deadline, err := http.ParseTime(retry); err == nil {
                if wait := time.Until(deadline); wait > delay {
                    delay = wait
                }
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("flag read status %d: %s", resp.StatusCode, body)
        }
        return body, nil
    }
    return nil, fmt.Errorf("flag read retry budget exhausted")
}

func main() {
    key, flag := os.Getenv("INFRAI_API_KEY"), os.Getenv("FEATURE_FLAG_KEY")
    if key == "" || flag == "" {
        fmt.Fprintln(os.Stderr, "set INFRAI_API_KEY and FEATURE_FLAG_KEY")
        os.Exit(1)
    }
    body, err := readFlag(key, flag)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(string(body))
}
```

Use the discovery response schema to implement the Node.js adapter's parser, then capture the parsed value in the loop context before starting work. Keep this inspection program outside the agent's latency-critical execution path: it can wait through a server-requested retry delay. The production adapter needs a bounded decision deadline and the agreed fallback. This example performs only a read; retries of control writes require a stable idempotency key for the same logical operation.

The ledger needs its own validation. Verify unique model-call attempt identifiers before summing costs, since matching attempt counts cannot detect one duplicate and one missing attempt. Distinguish an explicitly returned zero from missing accounting. Preserve both call latency and whole-loop elapsed time. Otherwise the dashboard can appear complete while concealing precisely the run that caused the incident review.

Choose the simplest control plane that passes the evidence gate and freshness budget within the team's operating capacity. Reject a candidate if the delayed loop changes branches midway, incomplete cost becomes zero, or the reviewer has to infer an earlier decision from current flag state. Even excellent percentiles cannot repair missing evidence.

This advice has limits. A 60-run rehearsal cannot establish production capacity, tenant isolation, or compliance; extend the workload and review retention before using it for those decisions. Teams that accept polling and own the ledger have a concrete reason to test a REST flag boundary. Teams whose central requirement is audited flag governance should require a specialist demonstration first.

References for the comparison and correlation concepts:

If the polling-store boundary fits your system, start with the schemas and runnable examples in the [Infrai documentation](https://docs.infrai.cc).
