An agent can finish its investigation while the customer is still stuck. That is an architecture problem.
An enterprise customer reports a strange problem. A newly enabled feature works for administrators, but a subset of users cannot access it. The admin console says the feature is enabled. Billing shows an active subscription. Support has already tried the usual steps.
An AI agent investigates, finds an entitlement that looks stale, refreshes it through an approved tool, receives a successful API response, and closes the case.
The next morning, the customer reports the same failure.
This is a hypothetical scenario, but it exposes a very real distinction. The agent completed an action. The customer’s problem was not resolved.
We have become good at giving agents capabilities: models that reason, tools that act, memory that preserves context, and frameworks that repeat the process. But capability is not the same as a reliable enterprise system. We still need to answer who owns the objective, which evidence can be trusted, when an action may execute, what survives a failure, and who decides that the result is good enough.
The agent isn’t the architecture.
I want to make that concrete, not just argue for another list of enterprise AI components. Let’s design one investigation from its outcome backward, and keep the responsibilities separate enough that the system remains understandable when something goes wrong.
First, is this actually an agent problem?
Before building anything, I’d ask what the agent is doing that deterministic software cannot do more simply.
If the problem is always caused by the same missing entitlement, a rule and an API call are better than an agent. But in our scenario, the evidence is ambiguous. The root cause might be a stale identity claim, an inconsistent feature flag, a tenant policy, a provisioning delay, or a regression introduced by a recent deployment. Different findings require different next questions. The sequence cannot be fully specified in advance.
That is where agentic reasoning earns its place: forming hypotheses, choosing the next diagnostic tool, testing a theory against fresh evidence, and changing course when the theory fails.
The agent does not need to own every step. It should not become the source of truth for subscriptions, permissions, or customer success. The architecture can give it room to investigate while keeping consequential decisions and acceptance deterministic.
Start with an outcome contract
“Resolve the access issue” sounds reasonable until an agent interprets a successful API response as resolution. I’d make the contract explicit before selecting the model or framework.
For this case, successful resolution means the affected user cohort can perform the actual feature operation under the intended tenant policy, supported by fresh technical evidence and confirmation from the customer or an authorized representative. If that cannot be established, the correct outcome is an evidence-backed escalation, not a closed case.
A small, versioned contract could look like this:
case_type: feature-access-investigation
contract_version: 1
tenant_id: tenant-042
affected_cohort: regional-analysts
business_operation: open-advanced-report
success:
required_evidence:
- scoped_authorized_probe_passed
- recent_access_errors_cleared
- customer_confirmation
max_probe_age_minutes: 10
min_post_change_attempts: 1 # Set by business risk and cohort coverage
required_operation_successes: true
unresolved_findings: 0
execution:
read_only_diagnostics: automatic
customer_impacting_changes: approval_required
max_diagnostic_steps: 8
max_steps_without_new_evidence: 2
uncertain_side_effect: reconcile_before_retry
terminal_states:
- VERIFIED_RESOLVED
- NEEDS_REVIEW
- FAILED
- CANCELLED
Illustrative design contract, not a deployment configuration. In a real system, the business owner must specify the affected cohort, authorized probe identity, error definitions, time window, and acceptable evidence.
A few lines change the whole conversation. The agent can try different diagnostic paths, but cannot redefine success. A technically successful entitlement refresh is not sufficient. A test with an administrator account is not sufficient evidence for users who have a different role. Zero errors without successful post-change activity is not evidence of recovery. And when customer confirmation is required but missing, the system must say so.
Purpose is not a paragraph in the system prompt. It is a completion condition the surrounding system can enforce.
Design the loop around evidence, not repeated model calls
An agent loop is often summarized as observe, reason, act, repeat. I find that description useful for a demo and incomplete for production.
I think about three related engineering disciplines. Prompt engineering shapes one model interaction. Context engineering determines the information the model sees. Loop engineering defines how the system makes progress across reasoning steps, tool calls, restarts, and failures.
For this incident, the execution cycle is:
Load state, assemble trusted evidence, propose a diagnostic step, authorize it, execute, record the result, check progress, then continue or terminate.
Figure 1. The agent chooses diagnostic steps. The surrounding architecture owns state, tool boundaries, and the definition of success.
Suppose the agent sees an active entitlement but a failed request containing an older identity claim. It may investigate token freshness next. If the claim is current, it can examine a feature flag or a recent rollout. The loop is adaptive because the evidence determines which tool matters next.
Here is how the investigation could unfold. The evidence drives the next question; the controller still enforces the boundaries.
| Evidence observed | Agent’s next step | What the architecture controls |
|---|---|---|
The subscription is active, but analysts receive 403 responses | Inspect effective identity claims for affected requests | Read-only access to scoped identity and access traces |
| Newly issued claims are correct | Compare feature-flag evaluations and tenant policy for that cohort | Current source-system evidence, not remembered diagnoses |
| A flag is disabled for only the affected cohort | Propose a narrowly scoped correction | Approval and enforced tool policy before a customer-impacting change |
| The approved change is confirmed | Request independent testing of the feature as an affected-role user | A fresh, authorized cohort probe and post-change activity checks |
| The probe succeeds but an affected user still reports failure | Keep investigating or escalate | No automatic VERIFIED_RESOLVED transition |
The agent’s value is choosing which diagnostic branch to explore. The controller’s value is making the resulting actions and conclusions defensible.
But the controller still owns four guarantees:
- Durability. Every significant observation, pending action, external operation ID, and checkpoint survives a session restart.
- Safe retries. A timeout after a tool call is an unknown outcome, not proof that nothing happened. Inspect the external operation before repeating a state-changing request.
- Progress limits. Repeating the same hypothesis without new evidence consumes a budget. It does not count as investigation progress.
- Terminal states. Verified resolution, review, failure, and cancellation are distinct. A response that says “fixed” cannot set
VERIFIED_RESOLVEDby itself.
An agent runtime may manage its internal reasoning loop. That does not mean it automatically owns durable business-process completion. Keeping those two lifecycles separate is one of the most useful architectural choices in this design.
Treat context, memory, and state as different responsibilities
I would not solve every continuity problem by adding more memory. The most dangerous failure is often not forgetting. It is remembering an earlier explanation as if it were current evidence.
| Concern | What belongs there | What makes it trustworthy |
|---|---|---|
| Execution state | Case status, completed checks, pending approvals, operation IDs, checkpoints | An authoritative durable record with conditional transitions |
| Working context | Current access traces, relevant entitlement data, recent changes, the next question | Fresh, scoped retrieval from authoritative sources |
| Long-term memory | Previously validated diagnostic patterns and recurring failure signatures | Provenance, lifecycle rules, and revalidation |
| Shared context | Structured findings handed between investigators or teams | Ownership, versioning, evidence references, and explicit uncertainty |
Imagine that last month’s access failure was caused by a feature flag. The agent remembers the pattern. That is useful, but it is not evidence that today’s failure has the same cause. A new identity-policy change could produce identical symptoms.
I would also avoid passing full agent conversation histories to other agents. If a specialist is asked to investigate identity behavior, give it an artifact such as this:
{
"case_id": "CASE-042",
"tenant_id": "tenant-042",
"finding_version": 3,
"finding_type": "hypothesis",
"hypothesis": "Affected requests contain outdated role claims",
"evidence_refs": ["trace://auth/1842", "audit://idp/2026-11-01"],
"observed_at": "2026-11-01T15:30:00Z",
"open_question": "Are newly issued tokens also affected?"
}
Illustrative values. This is a hypothesis with traceable evidence, not a validated root cause. The next investigator can test it without inheriting a long, potentially misleading transcript. If the finding changes, the artifact version changes. If it is contradicted, it should be marked as superseded.
Long-term memory needs similar rules: who can write it, whose cases it can describe, when it expires, and how an incorrect conclusion gets corrected. Start without cross-customer memory unless you have a clear benefit and a defensible isolation model.
Put ownership in code and services, not in the agent’s confidence
This is the implementation boundary I would expect a Principal Architect to define before production.
| Responsibility | Owner | Example implementation |
|---|---|---|
| Investigation plan and tool selection | Agent runtime | Managed agent loop or code-defined agent |
| Case state and terminal transitions | Workflow controller | Durable state machine and conditional-write ledger |
| Current facts | Source systems | Identity provider, entitlement service, feature-flag service, traces |
| Tool permissions | Enforcement boundary | Governed gateway, scoped identity, application policy |
| Shared findings | Versioned artifact store | Typed records with source references and ownership |
| Resolution evidence | Independent verifier | Authorized end-to-end probe and error-rate checks |
| Business acceptance | Customer-facing owner | Customer confirmation or documented review |
For uncertain side effects, the rule is straightforward. Record the intended operation with a stable request identity before calling the tool. If the response is lost, inspect the external operation’s status. Retry only if the API’s idempotency guarantees make replay safe. Otherwise, stop and reconcile.
That guarantee matters even if the agent can reason brilliantly. It is ordinary distributed-systems engineering, and it should not depend on what the model remembers.
What I would build on AWS
On AWS, I would start with Amazon Bedrock AgentCore for the agent-specific responsibilities, not bolt a model onto a traditional workflow engine and call that an agent platform.
AgentCore Harness provides a managed reasoning and tool-use loop, powered by Strands Agents. It runs on the AgentCore Runtime infrastructure. When I need custom loop logic or a different framework, I can run my own agent code on AgentCore Runtime, for example with Strands Agents or LangGraph. The choice is who owns the agent loop, not whether Harness and Runtime are unrelated services.
For this investigation, AgentCore Gateway, Identity, and Policy can provide governed tool access. AgentCore Memory may help with session continuity and approved historical patterns. AgentCore Observability and Evaluations can show how the agent investigated and where its decisions degraded. These services are modular, so I would start with the capabilities that solve an actual problem rather than deploy the entire catalog.
Figure 2. AgentCore runs the agent-specific work. The business controller and independent verifier remain accountable for the result.
There are two valid Step Functions integration directions, and I would choose based on who owns the business process. Workflow-driven: Step Functions owns approvals, durable case progression, and recovery, invoking AgentCore Harness for bounded diagnostic reasoning. Its optimized InvokeHarness task is request-response, with a 15-minute task limit, so the controller must account for a harness that can continue running after a task timeout. Agent-driven: AgentCore chooses when to call an existing Standard or Express Step Functions workflow exposed as an MCP tool through AgentCore Gateway. That keeps an approved operational sequence deterministic even when the decision to invoke it is adaptive.
For a short read-only investigation, neither pattern is necessarily needed. DynamoDB can hold authoritative case and operation state, while versioned S3 artifacts preserve evidence. The exact storage choice matters less than consistency, access boundaries, and recovery semantics.
There are two easy mistakes here. First, AgentCore Memory is not a replacement for an authoritative operation ledger. Second, an AgentCore Gateway policy only protects calls that actually go through the governed route. For consequential tools, associate the policy engine in ENFORCE mode, use active policies, and tightly restrict who may change Gateway configuration. LOG_ONLY observes decisions; it does not block actions. Keep privileges narrow and close any direct path that could bypass the gateway. For a deeper treatment of action authorization, see Permission Is Not Approval.
A workflow invoking InvokeHarness receives the final assistant message, not the intermediate tool-use record, so the agent must persist structured findings and evidence references separately. For a Gateway Step Functions target, pin the state machine on start operations, restrict execution operations through the Gateway service role, and treat callback task tokens as secrets. A workflow’s success response is still not proof that the customer’s issue is resolved. AWS documents these connector boundaries.
The same architecture can use other agent runtimes, Temporal or another workflow engine, and PostgreSQL instead of DynamoDB. The design contract comes first. The vendor choice comes second.
Make verification independent of the investigator
Return to the original problem. The agent refreshed an entitlement and received a successful tool response. How do we know the affected users can actually use the feature?
A verifier should execute an authorized end-to-end probe using a test identity with the same effective permissions as the affected cohort, then read operational evidence collected after the remediation. It must also retrieve customer confirmation through an authenticated support channel. Those checks need separate permissions and data paths from the investigating agent.
A passing probe alone is not enough. Imagine a metric showing zero access denials while feature requests return server errors, or no real users have tried the feature. Neither establishes recovery. The verifier therefore checks successful, operation-scoped activity after the change, not just an absence of authorization errors. Similarly, a successful test from before the change cannot prove the change worked.
The code below implements the decision boundary, not an AgentCore SDK integration. It deliberately reads the case, runs a scoped probe, queries recent operational activity, and retrieves customer confirmation through separately controlled adapters. Those adapters must authenticate upstream systems, isolate tenants, and validate the probe identity. The example assumes a schema-validated, versioned outcome contract.
from dataclasses import dataclass
from datetime import datetime, timedelta
@dataclass(frozen=True)
class ResolutionDecision:
state: str
expected_case_version: int | None = None
change_id: str | None = None
def decide_resolution(contract, case_id, sources, now):
"""Decide from separately sourced evidence, never the agent's claim."""
def matches(record, expected):
return isinstance(record, dict) and all(
record.get(key) == value for key, value in expected.items())
def timestamp(value):
return isinstance(value, datetime) and value.tzinfo is not None
review = ResolutionDecision("NEEDS_REVIEW")
case = sources.read_case(case_id) or {}
scope = {"tenant_id": contract["tenant_id"],
"cohort": contract["affected_cohort"],
"operation": contract["business_operation"]}
if not matches(case, {"case_id": case_id, **scope,
"contract_version": contract["contract_version"],
"status": "CHANGE_CONFIRMED"}):
return review
if type(case.get("unresolved_findings")) is not int or case["unresolved_findings"] != 0:
return review
if type(case.get("version")) is not int or case["version"] < 1:
return review
change_id = case.get("change_id")
changed_at = case.get("change_confirmed_at")
age = contract["success"]["max_probe_age_minutes"]
minimum = contract["success"]["min_post_change_attempts"]
if (not isinstance(change_id, str) or not change_id
or not timestamp(now) or not timestamp(changed_at)
or not changed_at < now or type(age) is not int or age <= 0
or type(minimum) is not int or minimum < 1):
return review
def after_change(value):
return timestamp(value) and changed_at < value <= now
def fresh(value):
return after_change(value) and now - value <= timedelta(minutes=age)
# The adapters use separate credentials and authoritative systems.
probe = sources.run_cohort_probe(case_id, **scope) or {}
activity = sources.read_access_activity(change_id, changed_at, **scope) or {}
confirmation = sources.read_customer_confirmation(case_id) or {}
if (not matches(probe, {"case_id": case_id, "change_id": change_id, **scope,
"identity_scope": "affected-role-canary"})
or probe.get("passed") is not True
or not fresh(probe.get("checked_at"))):
return review
if not isinstance(activity, dict):
return review
counts = [activity.get(k) for k in ("attempts", "successful", "denied", "failed")]
window_start = activity.get("window_start")
if (not matches(activity, {"change_id": change_id, **scope})
or not all(type(n) is int and n >= 0 for n in counts)
or counts[0] != sum(counts[1:])
or counts[1] < minimum or counts[2] != 0 or counts[3] != 0
or not timestamp(window_start)
or not timestamp(activity.get("checked_at"))
or not changed_at <= window_start <= activity["checked_at"]
or not fresh(activity.get("checked_at"))):
return review
if (not matches(confirmation, {"case_id": case_id, "change_id": change_id,
**scope, "result": "working"})
or confirmation.get("authenticated") is not True
or not after_change(confirmation.get("confirmed_at"))):
return review
return ResolutionDecision("VERIFIED_RESOLVED", case["version"], change_id)
The checks reject a stale or wrong-cohort probe, a mismatched case or remediation, an unconfirmed customer outcome, and zero meaningful activity. They also reject a subtler false positive: three post-change requests with no authorization denials but three HTTP 500 errors. Successful access is not equivalent to the absence of 403 responses.
This code returns a verification decision, including the expected case version and change identifier, not a durable state transition. The controller must preserve the supporting evidence and conditionally commit the terminal state only when both still match. If a second remediation changes the case during verification, the old decision must not close the new case. Otherwise, concurrent remediation can invalidate a valid-looking result.
In production, the trusted adapters must establish their own source identity and provenance; simply returning fields with tenant_id and change_id is not authentication. Define enough post-change traffic and role coverage to support the business claim. A single passing canary is not proof that every user has recovered; the business owner must set cohort coverage and success thresholds accordingly. Here, successful means application-level completion of the specified feature operation, not merely an HTTP 200 response. Customer confirmation must come from an authenticated support channel, not from an agent-written field. The example is a reference policy, not a deployed end-to-end system.
Test the failures that an impressive demo hides
I’d rather see a short, uncomfortable failure-injection session than another smooth demonstration. Start with these:
| Failure injected | Expected behavior |
|---|---|
| Agent retries the same failed hypothesis | No-progress budget triggers a different path or review |
| Historical memory conflicts with current identity claims | Current authoritative evidence wins; memory remains a clue |
| An entitlement update times out after submission | Reconcile remote status before any unsafe repeat |
| A tool call is denied by policy | Stop or choose a permitted diagnostic path, never bypass enforcement |
| A probe passes for administrators but fails for the affected cohort | Do not resolve the case |
| Error metrics show zero failures but no post-change attempts | Reject the false positive; wait for meaningful evidence or escalate |
| The agent reports success but customer confirmation is missing | Keep the case in NEEDS_REVIEW |
| A model or prompt update changes diagnostic behavior | Re-run scenario and workflow-level regression tests |
What would I measure? The verified resolution rate, false closure rate, time to evidence-backed escalation, customer reopens, number of unnecessary tool calls, and total cost per verified outcome. Those tell me whether autonomy is actually improving operations. A high rate of successful tool calls tells me much less.
I would also keep agent-level evaluations separate from business acceptance. AgentCore Evaluations or another evaluation system can assess tool selection, instruction following, and diagnostic quality. The business verifier must establish whether the original customer problem has been resolved.
What I would ship first
I wouldn’t start with a multi-agent fleet or a persistent memory platform. I’d start with one narrowly scoped investigation, read-only diagnostics, a handful of trusted tools, a durable case record, and a deterministic resolution gate. Let the agent recommend an action; have the existing operational system approve and execute it.
Then expand based on evidence. Add long-term memory only if validated prior cases meaningfully improve diagnosis. Add a second specialist agent only if parallel investigation or a distinct permission boundary justifies the coordination cost. Add durable outer orchestration when approvals and long-running dependencies demand it.
At the next architecture review, I would ask the team to walk through one case where the agent’s most confident hypothesis is wrong, its last tool call has an uncertain outcome, and a customer still cannot use the feature. Can the system explain what happened, avoid making things worse, and refuse to declare victory?
If it can, we’re getting closer to a production architecture. If it can’t, adding another agent won’t fix it.
An agent can finish the investigation. The architecture must prove the customer can use the feature.