Dogwood: temporal policy conditions for AI agent authorization
Reference parser and interpreter for the Dogwood policy language
At a glance
- What is it?
- Dogwood is a Cedar-derived policy language that adds since, formerly and windowed aggregations, plus a reference Rust interpreter for checking semantics. The interpreter is explicitly not an authorization engine, and the README says so.
- Who is it for?
- Adopt Dogwood if you need to reason about time-windowed agent authorization before committing to an engine: the CLI's validate, lower and replay commands give you a semantics check and a Cedar lowering you can inspect. Do not adopt it as your enforcement point.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap Dogwood targets: Cedar has no memory of what happened before
Cedar policies evaluate a single request. The principal, action, resource and context are all present at decision time, and nothing in the language reaches backward into a history of prior events. For a human-driven system that is usually fine. For an agent that calls tools in a loop, it is not: the question is rarely "may this agent read this file" and much more often "may this agent read this file, given that it authenticated eleven minutes ago and has already issued forty writes this hour."
Dogwood is a governance language for that second class of question. It keeps Cedar's permit and forbid structure and its when and unless clauses, then adds a temporal layer: since, formerly, once, and aggregations over a recent event window. The README's example pairs an ordinary amount check with a temporal one, requiring that a matching approval request occurred within the last hour. The intended audience is people writing authorization rules for agents and their tools who have already concluded that stateless checks are not enough, and who want to test what a temporal rule actually means before wiring it into anything that enforces.
How Dogwood lowers temporal conditions into plain Cedar context slots
The mechanism is compile-to-Cedar. A Dogwood policy is parsed, then lowered to a standard Cedar policy in which the temporal predicate has been replaced by a context slot. The README shows this directly: a permit whose body reads when temporal { formerly within 1h ... } becomes, after lowering, a permit with when { context.policy_0__temporal_0 }. The temporal reasoning does not happen inside Cedar. It happens before evaluation, when Dogwood consults the event history and decides whether that slot is true, then hands Cedar a policy that is ordinary and stateless.
The two engines are separate and swappable. The policy engine can be local Cedar or a remote policy store. The temporal engine can be in-memory or database-backed. Information providers, written as Rhai scripts, inject computed facts at evaluation time as guardrail context fields, which is how a value that is not in the raw request reaches a Cedar condition. This split is the interesting design decision: it means the temporal semantics are defined by Dogwood but enforced by whatever Cedar implementation you already run, and it means the trace store is a pluggable component rather than something baked into the parser. It also means the lowered policy is only as correct as the slot-filling step in front of it.
Installing the dogwood CLI and replaying a first policy
The README documents the CLI as three subcommands: validate, lower and replay. The repository layout places the binary in dogwood-cli/, described there as a thin shell over the frontend's public surface, and the guide chapter dogwood-docs/guide/12-cli.md is the reference. The quick start gives the invocation shapes directly.
Validation checks a policy against its schemas before you run anything:
# Validate a policy against its schemas:
dogwood validate policy.dw --policy-schema schema.cedarschemaA passing run prints OK: validation passed with no errors or warnings, per the worked example. Next, lower the policy to see exactly what Cedar will receive, including the generated context slot:
dogwood lower policy.dw --policy-schema schema.cedarschema --emit bothThe README's read_after_login example uses the same command with --emit cedar-policies and produces a permit whose condition is when { context.policy_0__temporal_0 }. That slot name is what your runtime has to fill. Finally, replay a trace to see verdicts over time:
dogwood replay dogwood-docs/examples/read_after_login/policy.dw \
--policy-schema dogwood-docs/examples/read_after_login/schema.cedarschema \
--trace dogwood-docs/examples/read_after_login/trace.logWith three events (login at t=0, read at t=10s, read at t=2h) the documented output is DENY at time point 0, ALLOW at time point 1 with the matching rule index, and DENY at time point 2. The first line is the login itself, not a read request. The third is the interesting one: the login has aged out of the one-hour window, so the temporal predicate no longer holds. If you are evaluating the language, that three-line output is the fastest way to see what formerly within 1h actually means.
For library use, the README points at the dogwood-language crate, added by git dependency, with the API overview in dogwood-language/README.md and workflow details in dogwood-docs/guide/07-api-and-workflow.md. The workspace declares version 1.0.0 and edition 2024.
Where the reference interpreter stops: timestamps, authentication and trace loss
The README is unusually direct that this repository is a reference interpreter for understanding semantics, not a production authorization engine. The limitations it lists are the ones that would quietly turn a correct policy into a wrong decision.
Event timestamp integrity: the interpreter accepts timestamps as given and does not validate them. Every temporal predicate is a comparison against those timestamps, so a caller who can supply a stale or future timestamp controls the window. Event authentication is absent entirely; a production implementation is expected to bind the authenticated caller identity as the principal before submitting events. Trace management is the third: the built-in InMemoryTemporalEngine has no eviction and no size cap, and because the interpreter is purely in memory, its trace is gone after a crash or restart. A long-running agent generates events continuously, so an unbounded store is a memory question, not a theoretical one.
Two failure modes are worth naming separately because they fail quietly. Fields needed by both temporal predicates and Cedar conditions must be supplied to both the logged bag via .field() and the request_context bag via .request_context(); supplying only one silently weakens the temporal check or the Cedar check. And events must use the same qualified action format as the policies, for example "{ServiceName}::Action::Transfer" rather than "Transfer". A mismatch means temporal predicates do not match while Cedar may still authorize the action. In both cases you get a verdict, not an error. If your policies depend on temporal conditions, a DENY caused by an action-name mismatch is indistinguishable at the output level from a DENY caused by the policy itself.
Dogwood against Cedar alone, and against a policy store with its own history
The honest comparison is Cedar by itself. Cedar is the substrate Dogwood lowers to, so the difference is entirely in the temporal layer and the providers. With Cedar alone you would push the history check into the application: query your event store, compute a boolean, and pass it in as a context field. That works, and it keeps the policy language stateless and easy to reason about. What you give up is that the rule is no longer expressed in the policy. The one-hour window lives in application code, is not validated against a schema, and cannot be replayed from a trace file. Dogwood's replay subcommand is the concrete payoff of putting it in the language instead.
The other alternative is a remote policy store that already maintains its own state and history. Dogwood anticipates this by making the policy engine pluggable, so a remote store is one backend choice rather than a competing product. The trade-off is that you inherit the store's evaluation model and its notion of when events become visible, and Dogwood's temporal semantics then have to be reproduced in a layer the reference interpreter does not implement. If your history is already durable and queryable in that store, the value Dogwood adds is the syntax and the lowering, not the storage.
Licence, maintenance and the cost of tracking the language
Dogwood is Apache-2.0, with a LICENSE and a NOTICE file at the repository root. Apache-2.0 is permissive and includes an explicit patent grant, which matters for a language you might embed in a product. It also carries attribution and notice-retention obligations, so if you vendor or redistribute the crate, keep LICENSE and NOTICE intact and check whether the NOTICE content changes between revisions. That is a description of the licence, not legal advice; your own counsel decides what your distribution requires.
The last push to the default branch was on 2026-09-17. No releases were retrieved for this repository, so there is no published version history to pin against and no changelog entries to read here, even though a CHANGELOG.md exists at the root. The workspace version is 1.0.0 and the crate is configured to publish to crates.io, but the README's library instructions use a git dependency rather than a crates.io version, which is the practical signal: expect to track a branch.
That has an upgrade cost specific to this project. Because Dogwood lowers to Cedar, a change in the lowering is a change in the context slot names your runtime fills. A policy that lowered to context.policy_0__temporal_0 under one revision could lower differently under the next, and nothing in the policy text would have changed. If you build on this, the artifact to pin and diff is the output of dogwood lower, not the .dw source. The dogwood-docs crate is worth noting here: its test harness runs each documented example bundle through the dogwood binary, so an example that stops working is a build failure. That is a real consistency guarantee for the examples, and it is the closest thing to a compatibility check available without release notes.
Editorial conclusion
Adopt Dogwood if you need to reason about time-windowed agent authorization before committing to an engine: the CLI's validate, lower and replay commands give you a semantics check and a Cedar lowering you can inspect. Do not adopt it as your enforcement point. The README states the reference interpreter is not intended for production use, and lists unvalidated event timestamps, no event authentication, no eviction in the in-memory temporal engine, and traces lost on restart. Before building on it, verify three things in the repository: whether the temporal backend you need is implemented or only pluggable, whether the schema files under dogwood-language/configuration/ cover your action names, and whether the lowered context.policy_N__temporal_N slots are something your runtime can populate. Start with dogwood replay on your own trace, because a silent non-match between your event action names and your qualified policy action names produces a DENY that looks like a policy decision rather than a data error.
Frequently asked questions
What is the Dogwood policy language?
It is a governance language for AI agents and their tools that supports Cedar policies and adds temporal conditions such as since, formerly, once and aggregations over an agent's recent events. This repository holds a reference interpreter for the language, which the README states is not intended for production use.
How do I use Dogwood to check a policy?
The README gives three CLI subcommands: dogwood validate to check a policy against its schemas, dogwood lower to emit the Cedar form, and dogwood replay to run a policy against a trace of events and print verdicts. The read_after_login example under dogwood-docs/examples/ shows all three.
Can I use the Dogwood reference interpreter in production?
No. The README states plainly that the reference interpreter is not intended to be used directly as an authorization engine, and lists unvalidated event timestamps, no event authentication, and an in-memory temporal engine with no eviction or size cap whose trace is lost after a crash or restart.
Why does a Dogwood replay show DENY when I expected ALLOW?
Two documented causes produce a silent non-match. Event fields needed by both temporal predicates and Cedar conditions must be supplied to both the logged bag via .field() and the request_context bag via .request_context(), and events must use the same qualified action format as the policies, such as "{ServiceName}::Action::Transfer" rather than "Transfer".
Does Dogwood replace Cedar?
No, it compiles to it. Policies lower to standard Cedar, and the temporal and provider fields become context.* slots filled at runtime, as shown by the read_after_login example lowering to when { context.policy_0__temporal_0 }. The policy engine and the temporal engine are separately pluggable.
How do I add Dogwood to a Rust project?
The README's library section adds dogwood-language to Cargo.toml as a git dependency pointing at the repository, and points to dogwood-language/README.md for the API overview and dogwood-docs/guide/07-api-and-workflow.md for detailed usage.
Community notes