Skip to content

Policy enforcement (OPA / Rego)

tako-governance wraps regorus 0.9 to evaluate Rego policy against three enforcement points in the agent loop:

Stage Decision space
PreChat Allow / Deny / RedactMessages / ForceModel
PreTool Allow / Deny / RequireApproval
PostChat Allow / RedactResponse

A Deny propagates as TakoError::PolicyDenied; RequireApproval short-circuits with the same error today (Phase 3 will add an approval flow).

Bundle a policy

use tako_governance::OpaBundle;
let bundle = OpaBundle::from_string(r#"
package tako.policy

default allow = true

# Block shell.exec for tenants without admin role
deny[msg] {
    input.stage == "pre_tool"
    input.tool == "shell.exec"
    not input.principal.roles[_] == "admin"
    msg := sprintf("shell.exec requires admin (tenant %v)", [input.principal.tenant_id])
}
"#)?;

Bundles are content-addressed by SHA-256 of the source string; the compiled regorus::Engine is cached, so re-creating an OpaBundle from the same string is cheap.

Wire it into the orchestrator

let agent = SingleAgent::builder()
    .provider(provider)
    .policy(Arc::new(bundle.into_engine()))
    .build()?;

Both SingleAgent and Conductor accept Option<Arc<dyn PolicyEngine>>. Without a policy, the orchestrator runs through the existing AllowAll impl (zero-cost: no policy calls happen at all).

Audit log

Every policy decision is recorded to a configurable AuditLog. Two backends ship:

  • AuditLog::jsonl(path) — append-only JSONL, suitable for SIEM ingestion; one decision per line with timestamp, principal, stage, decision.
  • AuditLog::in_memory() — for tests.

The JSONL format is intentionally stable; Phase-4 SIEM exporters will read the same shape.

See also