| description | Immutable Identity (id, type, roles, attrs) for the caller, attached to Context and propagated to children; consumed by ACL for type/role decisions; ContextFactory extracts from HTTP. |
|---|
Type: Implementation guide. Normative spec: PROTOCOL_SPEC §5.7 Context Object (
identitysub-schema).
The Identity System provides a structured representation of the caller's identity that flows through the execution pipeline. Every module call can carry an Identity describing who (or what) initiated the request — whether a human user, a service account, or an AI agent. The identity is immutable, attached to the Context, and consumed by the ACL System for access control decisions.
- Provide an immutable
Identitydata structure withid,type,roles, andattrsfields. - The
typefield MUST default to"user"and accept any string. Well-known types includeuser,service,ai,system, andanonymous. - The
rolesfield MUST be an immutable sequence (tuple/readonly array) of role name strings. - The
attrsfield MUST be an immutable dictionary for arbitrary key-value metadata. - Identity MUST be attachable to a
Contextand propagated to child contexts. - Identity MUST integrate with the ACL System for identity-type-based and role-based access control decisions.
- Provide a
ContextFactoryprotocol for web framework integrations to extractIdentityfrom HTTP requests.
=== "Python" ```python from dataclasses import dataclass from typing import Any
@dataclass(frozen=True)
class Identity:
id: str # Unique identifier
type: str = "user" # Identity type
roles: tuple[str, ...] = () # Immutable role list
attrs: dict[str, Any] = {} # Additional attributes (frozen via dataclass)
```
=== "TypeScript" ```typescript interface Identity { readonly id: string; readonly type: string; // Default: "user" readonly roles: readonly string[]; readonly attrs: Readonly<Record<string, unknown>>; }
function createIdentity(
id: string,
type?: string, // Default: "user"
roles?: string[],
attrs?: Record<string, unknown>,
): Identity;
// Returns a frozen Identity object
```
=== "Rust" ```rust use std::collections::HashMap; use apcore::Identity;
// Fields are private; use getters to access
let identity = Identity::new(
"user-123".to_string(),
"user".to_string(),
vec!["admin".to_string()],
HashMap::new(),
);
identity.id() // -> &str
identity.identity_type() // -> &str (default: "user")
identity.roles() // -> &[String]
identity.attrs() // -> &HashMap<String, Value>
```
| Type | Description | Typical Use |
|---|---|---|
user |
Human user (default) | Web app users, CLI operators |
service |
Service account | Microservices, background jobs |
ai |
AI agent or LLM | Autonomous agents, chatbots |
system |
Framework-internal | System modules, health checks |
anonymous |
No authenticated identity | Public endpoints, unauthenticated callers |
!!! note
The type field is a free-form string. The values above are the well-known conventions surfaced in the JSON Schema examples (protocol-spec.md §5.7). Applications MAY define custom types — implementations do not validate the value against the conventions list.
Identity is a value type. Equality is structural — two identities are equal iff id, type, roles, and attrs are equal as deep value comparisons. Hashability is implementation-defined per language:
- Rust:
IdentityderivesHashandEq; safe to use as aHashMapkey. - Python:
Identityis a frozen dataclass, but theattrs: dictfield makes it not hashable by default; cross-language code SHOULD NOT rely onhash(identity)portability. - TypeScript: object literal; equality and hashing are caller's responsibility (use a stable serialization or a structural-equality helper).
Resolved per docs/spec/2026-05-decision-log.md D-26.
=== "Python" ```python from apcore.context import Context, Identity
# Create identity
admin = Identity(
id="admin@example.com",
type="user",
roles=("admin", "operator"),
attrs={"department": "engineering"},
)
# Attach to context
ctx = Context.create(identity=admin)
print(ctx.identity.id) # "admin@example.com"
print(ctx.identity.roles) # ("admin", "operator")
# Identity propagates to child contexts
child = ctx.child("target.module")
assert child.identity is ctx.identity
```
=== "TypeScript" ```typescript import { Context, createIdentity } from "apcore-js";
// Create identity
const admin = createIdentity(
"admin@example.com",
"user",
["admin", "operator"],
{ department: "engineering" },
);
// Attach to context
const ctx = Context.create(admin);
console.log(ctx.identity?.id); // "admin@example.com"
console.log(ctx.identity?.roles); // ["admin", "operator"]
// Identity propagates to child contexts
const child = ctx.child("target.module");
// child.identity === ctx.identity
```
=== "Rust" ```rust use apcore::context::{Context, Identity}; use std::collections::HashMap;
// Create identity
let admin = Identity::new(
"admin@example.com".to_string(),
"user".to_string(),
vec!["admin".to_string(), "operator".to_string()],
HashMap::from([("department".to_string(), serde_json::json!("engineering"))]),
);
// Attach to context
let ctx = Context::create(Some(admin), None, None, None, Value::Null, None);
println!("{}", ctx.identity.as_ref().unwrap().id()); // "admin@example.com"
// Identity propagates to child contexts
let child = ctx.child("target.module");
```
The ACL System uses the Identity for access control decisions. ACL rules can match against identity properties:
Identity type conditions:
rules:
- callers: ["*"]
targets: ["admin.*"]
effect: allow
conditions:
identity_types: ["user"] # Only human users can call admin modulesRole-based conditions:
rules:
- callers: ["*"]
targets: ["billing.*"]
effect: allow
conditions:
roles: ["finance", "admin"] # Requires one of these rolesSpecial patterns:
| Pattern | Matches |
|---|---|
@external |
Calls with no identity (identity is None) |
@system |
Calls where identity.type == "system" |
See ACL System for full condition syntax.
The ContextFactory protocol enables web framework integrations to extract Identity from incoming HTTP requests:
=== "Python" ```python from apcore.context import ContextFactory, Context, Identity from typing import Protocol, runtime_checkable
@runtime_checkable
class ContextFactory(Protocol):
def create_context(self, request: Any) -> Context: ...
# Example: Django integration
class DjangoContextFactory:
def create_context(self, request) -> Context:
user = request.user
identity = Identity(
id=str(user.id),
type="user",
roles=tuple(user.groups.values_list("name", flat=True)),
attrs={"email": user.email},
)
return Context.create(identity=identity)
```
=== "TypeScript" ```typescript import { Context, createIdentity } from "apcore-js"; import type { ContextFactory } from "apcore-js";
// Example: Express integration
class ExpressContextFactory implements ContextFactory {
createContext(request: any): Context {
const user = request.user;
const identity = createIdentity(
user.id,
"user",
user.roles,
{ email: user.email },
);
return Context.create(identity);
}
}
```
=== "Rust" ```rust use apcore::context::{Context, ContextFactory, Identity}; use std::collections::HashMap;
struct AxumContextFactory;
impl ContextFactory for AxumContextFactory {
fn create_context(&self, request: &dyn std::any::Any) -> Context<serde_json::Value> {
// Extract identity from framework-specific request type
let identity = Identity::new(
"extracted-user-id".to_string(),
"user".to_string(),
vec!["viewer".to_string()],
HashMap::new(),
);
Context::create(Some(identity), None, None, None, Value::Null, None)
}
}
```
Identity is included when a Context is serialized (e.g., for distributed tracing across service boundaries):
{
"trace_id": "a1b2c3d4-...",
"identity": {
"id": "admin@example.com",
"type": "user",
"roles": ["admin", "operator"],
"attrs": {"department": "engineering"}
},
"_context_version": 1
}On deserialization, the Identity is reconstructed from the serialized form.
- Context — Identity is a field on the Context object.
- ACL System — Consumes identity type and roles for access control decisions.
- Observability — Identity information (id, type) is included in trace spans and structured logs.
??? info "Python SDK reference"
The following table is not a protocol requirement — it documents the Python SDK's source layout for implementers/users of apcore-python.
**Source files:**
| File | Purpose |
|------|---------|
| `src/apcore/context.py` | `Identity`, `Context`, `ContextFactory` |
- Immutability tests verify that Identity fields cannot be modified after creation.
- Default tests verify that
typedefaults to"user"androles/attrsdefault to empty. - Context propagation tests verify that Identity propagates through
Context.child(). - Serialization tests verify round-trip serialization/deserialization of Identity within Context.
- ACL integration tests verify that identity type conditions and role conditions produce correct allow/deny decisions.
- ContextFactory tests verify that custom factories produce valid Context objects with correctly extracted Identity.
identity(Identity, optional) — caller identity; defaults to@externalwhen absentcaller_id(str/string/&str, optional) — caller module ID for call-chain trackingdata(dict/object/Value, optional) — initial context data payload
- No errors raised (invalid identity fields are sanitized, not rejected)
- On success:
Context— initialized execution context with assigned trace ID and caller identity
- async: false
- thread_safe: true
- pure: false (generates a new trace ID on each call; not idempotent)
- idempotent: false