Governing Agent Behavior Over Time: Dogwood Temporal Policies in Amazon Bedrock AgentCore

The gap in point-in-time authorization

AgentCore Policy decides, on every tool call, whether an agent’s action is allowed. It launched with Cedar, which is fast, readable, and analyzable. Cedar also gives a guarantee that audit and enforcement depend on: identical requests yield identical decisions, regardless of evaluation order or system state.

That guarantee comes from Cedar being stateless by design. Each request is evaluated in isolation, with no dependency on what came before. A Cedar policy can inspect the current request’s parameters and the caller’s claims, and nothing else.

This draws a safety envelope around any single action. It cannot describe a sequence of them.

Consider an agent with a search tool. Cedar can enforce “no more than 10 results per call.” It cannot enforce “no more than 3 calls in 5 minutes” — that requires knowing how many calls already happened. The same limitation blocks a whole category of rules teams want, all of which need temporal control over actions:

  • Keep a cumulative total within a budget across a session
  • Limit number of calls within window or session
  • Only act on a value that a previous call returned
  • Stop using a tool after some other tool has been used

What Temporal Policies Add to AgentCore

AWS has extended AgentCore Policy with temporal policies, powered by Dogwood — a new open source governance language purpose-built for AI agents.

Dogwood embeds Cedar and adds a second kind of clause: when temporal { … }, whose body can reason about the agent’s recent event history. Its temporal operators are drawn from Metric First-Order Temporal Logic (MFOTL), a logic with roots in runtime verification — the discipline of checking a running system against a formal specification of how it should behave.

What this means in practice:

  • Existing Cedar policies keep working. Any syntactically valid Cedar policy is a valid Dogwood policy. No rewrite, no migration.
  • Enforcement stays at the gateway. The agent never sees the policy logic and cannot reason around it, regardless of how it is prompted or what defects it carries.
  • Decisions remain deterministic and deny-by-default, with forbid overriding permit, and every decision logged with the context behind it.
  • Evaluation is scoped to a session — a bounded sequence of actions identified by principal and session ID, with a 24-hour look-back window.

Dogwood Available capabilities

The operators you will reach for most:

  • formerly — did this happen in a window? Used positively it requires a prerequisite; inside a forbid it becomes a cool-down or a mutual exclusion.
  • since — does a condition hold continuously from some point? Expresses preconditions that stay in force until something revokes them, and approvals consumed by a single use.
  • count_within — how many times did this happen in a window? The basis for rate limiting.
  • sum_within — running total of a numeric input field. Enforces cumulative budgets, where each call is individually fine but the aggregate is not.
  • count_distinct_within — how many different values were seen? Caps variety rather than volume, so repeats are free and novelty costs.
  • bind — name an aggregate, then write an ordinary condition about it. Enables thresholds relative to the session’s own history rather than a fixed constant.
  • Event kinds — ::request (recorded per authorized request), ::response (on success, carrying output fields), ::error (on denial or tool failure, history-only). A predicate matching the same action it authorizes must use ::response; with ::request the current call matches its own event.
  • Inline composition — temporal { … } is an expression, so it sits inside an ordinary when { … } clause alongside plain Cedar and guardrail conditions; all must be satisfied.

Not yet available, but named on the roadmap in the Dogwood announcement: absolute-time windows (a quota that resets at midnight rather than a sliding window), liveness properties (what must eventually happen, not only what must not), and multi-agent orchestration.

What it takes to enable this

Adopting temporal policies requires a small number of changes beyond writing the policy itself.

1. Send the session ID header. Every request evaluated by a temporal policy must carry x-amzn-bedrock-agentcore-policy-session-id. You decide what constitutes a session — a user conversation, a multi-step task, a longer workflow — and narrower is better, since there can be no more than one concurrent authorization request per session.

If the header is absent, the gateway rejects the call outright: “Policy Evaluation rejected the request as invalid [sessionId is required when temporal policies are enabled].” Note also that a session is identified by session ID combined with the end user’s identity: two different identities presenting the same session ID are treated as entirely separate sessions.

2. Provide a baseline permit. The policy engine denies by default, and an action is only recorded in the session trajectory if it was permitted. Without a baseline permit, nothing is ever recorded, and no temporal predicate can match anything. A prerequisite tool in a sequencing policy needs its own permit for the same reason.

permit (
principal,
action == AgentCore::Action::"search-target___web_search",
resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/my-gateway-abc123xyz"
);

This one is plain Cedar, so it is submitted under the cedar definition key rather than policy.

3. Include eventResource: resource in every predicate. AgentCore requires it, scoping the match to the current request’s resource.

forbid (
principal,
action == AgentCore::Action::"search-target___web_search",
resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/my-gateway-abc123xyz"
)
when temporal {
count_distinct_within(k, 1h, AgentCore::Action::"search-target___web_search"::request{
eventResource: resource,
input.keywords: k
}) > 3
};

4. Enable gateway logging. Policy decisions and tool invocation results are only visible if a CloudWatch log delivery is configured for the gateway resource. This is separate from runtime logging. Without it, a failing call returns a generic error to the caller and nothing is recorded anywhere — the single highest-value thing to set up before writing policies.

5. Start in LOG_ONLY. The policy engine supports LOG_ONLY and ENFORCE. In LOG_ONLY, every decision is written to CloudWatch but nothing is blocked, letting you confirm each rule behaves as intended before switching to ENFORCE.

One operational note. Temporal policies are submitted under definition.policy.statement rather than the definition.cedar.statement used for stateless Cedar:

aws bedrock-agentcore-control create-policy \
 - policy-engine-id my-policy-engine-def456 \
 - name SearchRateLimit \
 - validation-mode FAIL_ON_ANY_FINDINGS \
 - definition '{"policy":{"statement":"forbid (…) when temporal { … };"}}'

AgentCore Dogwood Temporal Policy in Action

A session rate limit on a search tool: three calls per five-minute sliding window, with the fourth denied.

forbid (
principal,
action == AgentCore::Action::"search-target___web_search",
resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/my-gateway-abc123xyz"
)
when temporal {
count_within(5m, AgentCore::Action::"search-target___web_search"::request{
eventResource: resource,
input.keywords: _
}) > 3
};

Reading it: count_within counts matching events in the trailing five minutes and compares the result to three. The _ is a wildcard — the value of keywords is irrelevant, only that a call happened. Because this is a forbid, it overrides the baseline permit whenever the count is exceeded.

The count includes the current request, which is why > 3 allows three calls rather than four: on the fourth call the count reaches four and the forbid applies.

Matching ::request here is deliberate. Counting ::response would let an agent issue many concurrent calls before any of them settles, staying under the limit the whole time — agents make parallel tool calls, so this is not hypothetical. Counting requests closes that gap. (count_within is a standard-library macro; the same policy can be written directly against the underlying exists / count for / tp operators, which is how the AWS documentation presents it.)

Result

With the policy in ENFORCE mode, the gateway’s decision log across one session:

ALLOW temporal=true [baseline permit]
ALLOW temporal=true [baseline permit]
ALLOW temporal=true [baseline permit]
DENY temporal=true [search rate limit] <- 4th call in the window

temporal=true confirms the session header was received and the trajectory populated. The DENY names the rate-limit policy as the determining policy, which is what makes the decision auditable: a reviewer sees not just that a call was blocked, but which rule blocked it.

From the agent’s side, the fourth call fails and the agent reports that it cannot search:

Dogwood Temporal Policy in Action

The agent has no way to distinguish this from any other tool failure, and no way to work around it. That is the point — the boundary holds outside the agent’s reach.

One Caveat on Rate Limits Specifically

A session-scoped rate limit is a behavior-shaping control, not a security boundary. The caller supplies the session ID, so a determined caller can reset the count simply by starting a new session. Use this to keep a cooperative agent within sensible bounds; for a hard ceiling against a caller who controls their own session ID, use gateway rate limiting, which is keyed to the caller’s identity rather than a session they choose.

This caveat is specific to counting and summing within a session. Ordering and prerequisite policies are not affected the same way — starting a fresh session clears the history a permit depends on, so it denies rather than allows.

Conclusion

Temporal policies close a gap that has been filled with application code until now: rules about how an agent behaves over a sequence of actions, rather than whether one action is permissible in isolation. Moving that logic into the platform means it is written once, enforced consistently across every agent, and outside the reach of the agent it governs.

Worth being clear on scope. Temporal policies govern tool call sequences and rates; they do not inspect content. Bedrock Guardrails still handles content filtering — prompt injection, harmful categories, sensitive information — and the two compose rather than replace one another. Token and request throughput caps are a third, separate mechanism: gateway rate limiting, configured on the gateway itself.

References

Noor Sabahi | GenAI Practice Manager, New Math Data | AWS Ambassador

#AgentCore #AgenticAi #CedarPolicies #Dogwood #DogwoodTemporalPolicies #AWS #AgentCoreGateway