Non-human actors — bRRAIn Docs

Service and Agent sessions for robots, IoT, and automation: creation, workflows, audit trails, and rate limits.

Non-human actors

bRRAIn models non-human participants — robots, IoT sensors, batch jobs, AI agents — as first-class session types. This gives them distinct audit signatures, tailored rate limits, and explicit conflict-resolution rules.

Service vs Agent

| Attribute | Service | Agent | | --- | --- | --- | | Typical use | Cron jobs, ingesters, backend workers | Autonomous AI agents, robots, teleoperators | | Autonomy | Scripted, predictable | Adaptive, may write arbitrary content | | Conflict resolution | Escalates to supervisor user | Pauses, escalates immediately | | Rate limit class | High volume, bounded | Moderate volume, bursty | | Audit signature | svc:<name> | agt:<name> |

Use Service for any deterministic automation. Use Agent when the actor's behavior is not fully scripted — including LLM-driven agents with tool use.

Creating a Service session

svc, err := ws.CreateSession(sdk.SessionTypeService,
    sdk.WithMetadata(map[string]string{
        "name":     "nightly-ingester",
        "purpose":  "ETL from Postgres",
        "schedule": "0 2 * * *",
    }),
)
if err != nil {
    return err
}
defer svc.Close()

for _, row := range rows {
    svc.Store(row)
}

Service sessions inherit permissions from the key type (sk_svc_...). They can run indefinitely — svc.Close() is polite but not required.

Creating an Agent session

agt, _ := ws.CreateSession(sdk.SessionTypeAgent,
    sdk.WithMetadata(map[string]string{
        "name":       "mining-rig-3-teleoperator",
        "supervisor": "operator-alice@miningco.io",
    }),
)

Agent sessions require a supervisor — a human user who receives escalations. Any conflict detected in the Consolidator routes to the supervisor.

Autonomous workflows

A typical agent workflow:

for {
    reading := sensor.Read()
    if reading.Anomalous() {
        _, err := agt.Store(reading.ToRecord(), sdk.WithContext(map[string]string{
            "severity": reading.Severity(),
        }))
        if errors.Is(err, sdk.ErrAgentPaused) {
            // Supervisor has paused this agent; wait before next iteration.
            time.Sleep(1 * time.Minute)
            continue
        }
    }
    time.Sleep(reading.Period)
}

Error handling in automated processes

Because non-human actors run without a human in the loop, transient errors must be surfaced to someone. Use the built-in incident channel:

svc.ReportIncident(ctx, &sdk.Incident{
    Severity: sdk.IncidentError,
    Title:    "ETL source unreachable",
    Details:  err.Error(),
})

Incidents appear in the supervisor's dashboard and, for Agent sessions, trigger immediate pause.

Audit trails

Every operation is tagged with the session type and identity:

{
    "operation_id": "op_lh3f...",
    "actor":        "svc:nightly-ingester",
    "timestamp":    "2026-04-16T02:15:42Z",
    "action":       "store",
    "record_id":    "rec_lh3g..."
}

Audit queries can filter by actor type:

events, _ := client.Audit.Query(ctx, &sdk.AuditQuery{
    ActorType: sdk.ActorAgent,
    Since:     time.Now().Add(-24 * time.Hour),
})

Rate limits and quotas

| Actor type | Default operations / min | Burst | | --- | --- | --- | | User (Personal) | 600 | 1,200 | | Service | 6,000 | 18,000 | | Agent | 1,800 | 3,000 |

Exceeding the limit returns sdk.ErrRateLimit with a Retry-After hint. Workspace admins can raise limits per actor from the dashboard.

Conflict escalation

When an Agent session writes a record that conflicts with an existing one:

  1. The Consolidator detects the conflict and creates a conflict record (Zone 4).
  2. The Agent's write is paused (returns sdk.ErrAgentPaused).
  3. The supervisor is notified via the configured channels (email, Slack, SMS).
  4. On resolution (accept agent, accept human, or merge), the agent is unpaused.

Service sessions follow the same path but don't auto-pause unless the conflict threshold is exceeded (default: 3 unresolved conflicts within 15 minutes).