LaserData Cloud
Laser SDK

Governance

Apply managed permissions, runtime policy, and durable approval decisions

Governance controls who can act and which effects can proceed. Capability RBAC protects managed APIs. ActionGovernor applies policy before effects, and durable approval records support decisions made later. Access is denied by default, and a matching deny takes precedence.

Capability RBAC

RBAC means role-based access control. Managed grants use effect feature:action [on resource-pattern]. Roles bind them to the Iggy user identity stamped by the server.

const role: Role = {
  name: "support-reader",
  grants: [{
    effect: "allow",
    feature: "kv",
    action: "read",
    resource: { kind: "prefix", value: "support/" }
  }]
}

await laser.defineRole(role)
await laser.bindRoles(targetUser, [role.name])
use laser_sdk::rbac::{Action, Effect, Feature, Grant, Role, ResourcePattern};

let role = Role {
    name: "support-reader".to_owned(),
    grants: vec![Grant {
        effect: Effect::Allow,
        feature: Feature::Kv,
        action: Action::Read,
        resource: ResourcePattern::prefix("support/"),
    }],
};

laser.define_role(role).await?;
laser.bind_roles(target_user, vec!["support-reader".to_owned()]).await?;
grants = [
    ls.Grant(
        "kv",
        "read",
        resource_kind="prefix",
        resource_value="support/",
    )
]

await laser.define_role("support-reader", grants)
await laser.bind_roles(target_user, ["support-reader"])

The API provides these operations and rules:

  • laser.whoami() returns the caller's roles and effective grants.
  • list_roles, get_role, get_bindings, define_role, delete_role, and bind_roles manage roles and bindings.
  • A deny wins over an allow for the same feature, action, and resource match. This rule cannot be disabled.
  • Role names must satisfy a 64-byte character allowlist. Invalid names fail locally before a request.
  • Managed grants supplement Iggy's own permissions and are enforced at the streaming edge.

When an agent acts for a user, both grant sets must allow the action:

const allowed = delegatedAllow(
  agentGrants,
  userGrants,
  "kv",
  "write",
  "support/tickets/acme"
)
let allowed = delegated_allow(
    &agent_grants,
    &user_grants,
    Feature::Kv,
    Action::Write,
    Some("support/tickets/acme"),
);
allowed = ls.delegated_allow(
    agent_grants,
    user_grants,
    "kv",
    "write",
    "support/tickets/acme",
)

A user session cannot extend the agent's grants. The user's restrictions also remain effective through the agent.

A2A and MCP claims are evaluated before they reach AGDX. A wrong audience is rejected. A valid audience without a required scope receives a typed step-up challenge that names the missing scope.

Role bindings support updates guarded by revision. Rust uses bind_roles_expect_revision. TypeScript supplies expectRevision as the third argument to bindRoles, and Python supplies expect_revision to bind_roles. After a conflict, read the bindings again. New Iggy users receive no managed role automatically.

ActionGovernor

The agent feature provides ActionGovernor. It applies runtime policy before an effect, including budgets, rates, and deployment rules. RBAC determines the principal's permission separately.

Run budgets limit event counts, model calls, tool calls, elapsed time, and cost. They do not decide who can submit a run.

const run = await laser.runs().submitBudgeted(
  "governance-auditor",
  {
    maxEvents: 8n,
    maxModelCalls: 1n,
    maxToolCalls: 2n,
    maxWallClockMicros: 30_000_000n
  },
  body
)
let budget = RunBudget {
    max_events: Some(8),
    max_model_calls: Some(1),
    max_tool_calls: Some(2),
    max_wall_clock_micros: Some(30_000_000),
    ..Default::default()
};
let run = laser
    .runs()
    .submit_budgeted("governance-auditor", Some(body), budget)
    .await?;
budget = ls.RunBudget(
    max_events=8,
    max_model_calls=1,
    max_tool_calls=2,
    max_wall_clock_micros=30_000_000,
)
run = await laser.runs().submit_budgeted(
    "governance-auditor",
    input=body,
    budget=budget,
)

Durable approval records

Approvals use typed Intent, Vote, and Decision records. The application publishes them and combines their recorded results into a decision.

const now = BigInt(Date.now()) * 1_000n
const safety = AgentId.new("safety")
const intent = new Intent({
  conversation: ConversationId.new(),
  proposer: AgentId.new("planner"),
  body: new TextEncoder().encode("reserve inventory"),
  eligibleVoters: [safety],
  policy: { kind: "all" },
  policyVersion: 7n,
  atMicros: now,
  deadlineMicros: now + 30_000_000n
})

const vote = Vote.cast(intent, safety, VoteChoice.Allow)
const decision = decide(intent, [vote], BigInt(Date.now()) * 1_000n)
if (decision?.authorizes(intent)) {
  // apply the fenced effect, then persist the decision
}
use laser_sdk::intent::{decide, Intent, IntentPolicy, Vote, VoteChoice};

let intent = Intent::builder()
    .conversation(ConversationId::new())
    .proposer("planner".parse()?)
    .body(b"reserve inventory".to_vec())
    .eligible_voters(vec!["safety".parse()?])
    .policy(IntentPolicy::All)
    .policy_version(7)
    .deadline_micros(deadline)
    .build()?;

let vote = Vote::cast(&intent, "safety".parse()?, VoteChoice::Allow)?;
if let Some(decision) = decide(&intent, &[vote], now)? {
    if decision.authorizes(&intent)? {
        // apply the fenced effect, then persist the decision
    }
}
now = time.time_ns() // 1_000
intent = ls.Intent(
    conversation=ls.new_conversation_id(),
    proposer="planner",
    body=b"reserve inventory",
    eligible_voters=["safety"],
    policy=ls.IntentPolicy.all(),
    policy_version=7,
    deadline_micros=now + 30_000_000,
)

vote = ls.Vote.cast(intent, "safety", "allow")
decision = ls.decide(intent, [vote], time.time_ns() // 1_000)
if decision and decision.authorizes(intent):
    # apply the fenced effect, then persist the decision
    pass

The SDK evaluates an intent before publication and decision. Invalid policy fails immediately. A voter name is a claim within a record. Use signed principals or topic access rules when the application must trust voter identity.

Running it

Role and binding operations require the managed authz capability from Laser Stack or LaserData Cloud. Decision helpers and durable intent records work with Iggy alone.

On this page