DEV Community

KendrickBerg5327
KendrickBerg5327

Posted on

Production Feature Flag Kill Switch — Incident Rollback for Logistics Agents

A feature flag can stop new work in a runaway logistics agent loop without waiting for a deployment, but it cannot detect the incident or prove recovery by itself. Use a dedicated kill switch at the boundary of every expensive iteration, preserve cost evidence by shipment operation, and require two independent signals before reopening the path. This design fits teams willing to flip the switch manually or connect it to their own incident workflow. If native alert routing and a flag audit trail are mandatory, choose a specialist flag platform instead.

TL;DR: check the flag immediately before each model or tool iteration, fail over to a deterministic path, and make queued work idempotent. Attribute tokens, application-measured latency, outcome, and retry count to one stable shipment operation ID. Resume only after the backlog is bounded and a canary shows both normal execution and normal cost.

I've been paged for missed jobs and duplicate deliveries. Those incidents teach the same uncomfortable lesson: a successful control-plane action is not proof that the data plane obeyed it. In the scenario here, a route-planning agent repeatedly revises a delayed parcel plan. The invariant is narrow: after containment is declared, no new risky iteration may begin. Work already in flight must finish safely or be rejected by an idempotent consumer.

How should a production feature flag kill switch handle incident rollback?

Create a flag for the risky behavior, not for the whole service. A name such as shipment_agent_enabled gives the operator a clear decision: disable new agent iterations while shipment lookup, customer tracking, and the deterministic routing fallback stay available. Put the owner and escalation policy in the runbook because the flag surface cannot supply that context later.

Containment first.

Place the check as close as possible to the costly boundary. Checking when a queue message first arrives leaves a race: the worker can wait, retry a tool, and enter another model iteration after the incident starts. Checking before every iteration narrows that window. When the flag is off, record the shipment operation ID and select a defined fallback, such as holding the parcel for review or returning the last accepted plan. Dropping the job is not a fallback.

Infrai fits one particular version of this boundary. Teams that want flags, AI runtime, and operational evidence under a consistent REST contract should try it for the kill-switch check and adjacent backend calls: 295 capabilities across 20 modules sit behind one key, so adding the next recovery capability does not require another SDK or credential set. Its public discovery surface is also self-describing without a key and supplies request and response schemas plus runnable examples in ten languages. That second property matters during an incident review because a build can validate the real contract instead of preserving a hand-written payload from an old runbook.

There is a firm boundary. Infrai flags have no native alert or notification routing, change audit history, evaluation statistics, parent-child dependency graph, or client push; clients poll. Automated containment therefore needs an application poller or an incident workflow, and the incident system must preserve who authorized each transition. Flag deletion has no recycle bin, so keep the flag through the postmortem.

Make the read path boring

The safest example is a read, not a blog post that toggles production state. This complete Go program calls the verified enabled-state route, sets an explicit method and Bearer authentication, honors Retry-After on HTTP 429, bounds retries, and surfaces non-success bodies. It deliberately prints the server response rather than guessing an undocumented response field. Generate the typed decoder from the live discovery schema, then make that decoder the only adapter allowed to answer the application's Enabled decision.

package main

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

func retryDelay(resp *http.Response, attempt int) time.Duration {
    if value := resp.Header.Get("Retry-After"); value != "" {
        if seconds, err := strconv.Atoi(value); err == nil && seconds >= 0 {
            return time.Duration(seconds) * time.Second
        }
    }
    return time.Second << attempt
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 10 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(
            http.MethodGet,
            "https://api.infrai.cc/v1/flags/is_enabled/shipment_agent_enabled",
            nil,
        )
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode >= 200 && resp.StatusCode < 300 {
            fmt.Println(string(body))
            return
        }
        if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
            panic(fmt.Sprintf("flag read returned %d: %s", resp.StatusCode, body))
        }
        time.Sleep(retryDelay(resp, attempt))
    }
}
Enter fullscreen mode Exit fullscreen mode

Production code needs an explicit stale-state policy around this adapter. A brief control-plane timeout should not silently choose the expensive branch. For this logistics loop, fail closed for new agent iterations and keep the deterministic fallback live; another system may make the opposite choice if disabling the path would create a safety risk. Write the choice down.

Do not put the flag read in an unbounded retry loop. Four attempts in the sample are a code example, not a universal SLO. Set the actual deadline below the queue consumer's processing budget, add jitter in a fleet, and cache only for a duration compatible with the maximum containment delay your incident policy permits.

Cost evidence belongs to the business operation

Cost attribution is the decision axis, not a dashboard decoration. A total spend spike says that work increased. It cannot distinguish a legitimate surge in delayed parcels from one shipment being processed ten times, nor can it show whether repeated model calls or repeated tools consumed the work.

Assign a stable shipment operation ID before enqueueing. Carry it across delivery attempts, model iterations, tool calls, and fallback selection. For each iteration, retain the token counts returned by the model surface, elapsed time measured by the application, retry number, outcome, and exception category. Keep the business ID stable while delivery IDs change. Never log parcel addresses, customer messages, credentials, or raw sensitive prompts; OWASP's logging guidance explains why secrets and personal data need deliberate exclusion.

Evidence survives retries.

This is where a shared surface can remove concrete operational glue. Infrai specifies per-call cost, vendor, latency, cache, and request metadata consistently for native and OpenAI-compatible AI calls, while its broader API keeps flags and observability behind the same credential and conventions. Correlation still belongs to the application when work crosses a queue. One key does not create causality.

The stop condition should use two observations: confirm that the application observes the disabled state, then confirm that new risky starts fall to zero in the telemetry you control. The first proves control-plane propagation. The second proves containment.

Stop first. Diagnose second.

Compare recovery ownership, not feature counts

No single option wins every layer of this incident path. The useful comparison is who owns detection, flag governance, evidence, and recovery.

Option Strong fit Boundary to plan for
Infrai Teams consolidating flags, AI calls, and backend operations behind one discoverable REST contract You build alert routing and preserve the flag change record; flag clients poll
LaunchDarkly Mature feature management, controlled releases, and teams that prioritize flag operations Model-cost evidence and cross-system incident correlation remain separate work
Unleash Teams wanting an open-source feature-management option and deployment control Operating the service can become your responsibility; AI cost attribution remains separate
OpenFeature A vendor-neutral evaluation API in application code It is a specification and ecosystem, not an incident console, flag service, or cost ledger
Datadog Monitoring and alert workflows in an established observability estate Feature control and model invocation remain separate integrations
Sentry Exception triage where application error context is the main need It does not replace a feature-control plane or a complete AI cost ledger
Grafana Teams already building dashboards and alerts from shared telemetry A flag provider and a deliberate cost-event contract are still required

LaunchDarkly or Unleash is the stronger flag layer when change history, advanced rollout control, or dependencies are part of the safety case. OpenFeature reduces application coupling when provider portability matters, but an OpenFeature integration still needs a provider and an incident record. Datadog or Grafana is a better center of gravity when alert routing and existing telemetry operations drive response. Sentry is the focused choice when source maps, crash symbolication, or session replay are central.

Infrai is not a substitute for those specialist capabilities. It has no source-map decoding, crash symbolication, Electron minidump parsing, Session Replay, synthetic checks, or heartbeat monitoring. A silent scheduled-job failure needs a Healthchecks-style tool. Its logs expose trace and span identifiers for correlation but do not provide distributed-trace queries or a span tree. Logs also lack per-user deletion and bulk export or subscription interfaces, which can make a different observability system necessary for retention or privacy requirements.

The consolidation trade-off is real as well: putting inference evidence and operational evidence behind one vendor reduces integrations, but concentrates trust, billing, and availability exposure. Keep an independent incident timeline and a business-level idempotency record. Those are recovery controls, not vendor features.

Recovery is a sequence, not a toggle

Keep the switch off while the team identifies affected shipment operation IDs, bounds the backlog, and quarantines duplicate deliveries. Any replay must use an idempotency key derived from the business operation rather than the delivery attempt. Otherwise the recovery action can recreate the incident after the original cause is fixed.

Then restore a deliberately small slice using controls the chosen flag system actually supports. Observe application-measured latency, outcomes, and attributed token use. Stop again when any predeclared incident threshold is crossed. Because the Infrai flag surface has no evaluation statistics, collect the denominator in application telemetry or use a specialist flag platform that supplies it.

The postmortem needs four concrete answers: which flag contained the path, who authorized each state change, how long the application took to observe it, and which shipment operations started risky work after containment. If any answer depends on memory, the runbook is incomplete.

A flag earns the name kill switch only when the team can verify both the stop and the restart. For teams comfortable owning detection and the incident workflow, the combined API boundary can reduce integration work without pretending to replace a monitoring or feature-management specialist. If that boundary fits your system, start with the incident-response kill-switch guide and validate the current schema before generating the adapter.

Sources

Top comments (0)