Chio/Docs
LOGIN · JOIN

Authenticated identity

See how the trusted host binds caller, tenant, session, and epoch to each operation, and what happens when another worker copies one.

Concept chio@5f63e6e

On this page

One service account can carry many tasks, a failed worker can be replaced, and old work can be recovered after restart, while each operation keeps running as the caller who started it. The trusted host binds the caller, tenant, session, and authentication epoch to the operation before the kernel reads its arguments, and that binding stays with the operation through delegation and recovery.

Request metadata describes work and chooses no authority: an authenticated caller still needs permitted scope, a live grant, and current authorization for the effect. The examples are the Execution host's reviewer process and a swarm in which one worker presents another's task.

Caller bindingChoose a case.
workertrusted hostcredential + operationpath: lib.rsaptenant_idrepository-reviewisolation_epoch_idreview-epoch-1process_idreviewercapability_idcap-01a0f85a…operation_keyread-lib-v1operation + contextKdispatchdread_fileρprocess · attributionworkertrusted hostcredential + operationpath: lib.rsaprepository-reviewreview-epoch-1reviewercap-01a0f85a…read-lib-v1operation + contextKdispatchdread_fileρprocess · attribution

The credential selects the process; the host supplies the rest

The worker's credential selects the retained reviewer process, which carries its original capability. The host's security profile supplies the tenant and isolation epoch, and the arguments name only a path. The receipt records the process, the operation key read-lib-v1, and the capability's attribution. Execution.

One call through the repository host's worker service: the credential selects a retained process, the host supplies the tenant and epoch, and the kernel signs the receipt. The capability id is shortened; transport authentication, approvals, and recovery fences are left out.

Each identity outlives a different thing

A long task outlives worker processes and network connections, and its transport credential can change while its original operations still need recovery. Several tasks can share one service account and still hold separate grants and histories.

Chio keeps the identities apart. The tenant is an isolation decision, the session is authenticated interaction context, the operation is one retained piece of work, and the durable actor is distinct from the credential used to reach it. A host can replace a failed worker without making its task a newly entitled actor, and incident response gets a precise target, since a connection, a process, and a grant each need a different withdrawal.

The host establishes these bindings before it interprets tool arguments. A worker may request a file path or attach a tracing label; choosing another tenant or a sibling's authority would change whose work is performed, so that choice belongs to the host.

The host pins the session before it normalizes the call

The host authenticates the transport and pins the resulting session context, retaining tenant, agent, session anchor, and authentication epoch together. The kernel uses that context when it evaluates authority and signs evidence. A change in authentication creates a new binding, and requests already admitted keep the one they started with.

BindingEstablished bySubstitution it prevents
TenantAuthenticated session snapshotReading or spending another tenant's state
Agent and subjectAuthentication plus capability admissionUsing another principal's grant
Session and authentication epochHost session lifecycle, retained lineageRetagging past work after reauthentication
Target server and toolCapability grants and the verified registrySending valid authority to the wrong service
Capability digestThe original signed capabilityReplacing the grant during continuation
Request and intent commitmentsThe normalized original operationChanging arguments under an existing approval
Executor pinDeployment configuration and signed deliveryAccepting a result from another executor

In a multitenant service, request-local state ends with its request, and an old operation may be reconciled long after its HTTP request closed. Its retained context names the original tenant, whichever request happens to be running now.

Session-host start and lineage records

The session host captures an anchor snapshot and a lineage record together when a request starts. Adapters use that authenticated session context instead of rebuilding the values from request metadata.

crates/kernel/chio-kernel/src/session.rs724–727rust
pub struct SessionRequestStart {
    pub session: SessionAnchorSnapshot,
    pub lineage: RequestLineageRecord,
}
crates/kernel/chio-kernel/src/session.rs688–696rust
pub struct RequestLineageRecord {
    pub request_id: RequestId,
    pub session_anchor_id: String,
    pub auth_epoch: u64,
    pub parent_request_id: Option<RequestId>,
    pub operation_kind: OperationKind,
    pub started_at: u64,
    pub terminal_state: Option<OperationTerminalState>,
}

session_anchor_id and auth_epoch connect the request to the authentication state that admitted it. parent_request_id connects delegated or nested work to its parent, and operation_kind and the terminal state describe the operation's lifecycle. A display name or conversation title supplies none of these bindings.

When testing reconnect, retain the original record and compare the new authenticated context. A transport reconnect that resumes the same operation keeps its parent and authentication epoch; a new authenticated operation needs its own start record. Substitute another session's request ID and check that the retained relationship refuses it.

The repository host selects the process, and the arguments select a path

The Execution host opens the retained runtime, takes the tenant and isolation epoch from its trusted security profile, creates the reviewer process with its original capability, and issues the worker a connection credential. Tool arguments supply only the requested relative path.

security-execution/src/main.rsrust
fn worker_service(
    state: &Path,
    kernel: ChioKernel,
    capability: &CapabilityToken,
) -> Result<WorkerService> {
    let runtime = ProcessRuntime::open(state.join("process.db"), Arc::new(kernel))?
        .with_security_profile(ProcessSecurityProfile {
            tenant_id: "larkfield".into(),
            isolation_epoch_id: "review-epoch-1".into(),
            generation: 1,
        })?;
    runtime.create_root(
        "reviewer",
        capability,
        ProcessLimits {
            max_processes: 1,
            max_depth: 0,
            max_calls: 16,
            state: Default::default(),
        },
    )?;
    let service = WorkerService::new(runtime);
    service.revoke_credentials("reviewer")?;
    Ok(service)
}

The example owns one fixed tenant, larkfield; tenant selection is outside its API. On restart the service revokes the reviewer's old credentials before it publishes a fresh connection. The runtime ID reopens unchanged, the tenant and isolation epoch must match the retained profile, the process stays reviewer, and the capability ID and subject key are retained with current admission still required. The bearer is replaced and the previous one withdrawn, and an operation key is reused only for the same logical request.

Trace IDs, display names, and user tags serve correlation and decide nobody's right to spend, disclose, or execute. Chio commits to their bytes in the original request without promoting them into signed authority fields. A host can report that evaluation failed, while the kernel's signing identity, policy identity, tenant, and decision semantics stay the kernel's; the signed observation binds the normalized operation and the authenticated session.

Read the identities in the retained receipt

With the host running and the original read retained, inspect the identity fields and signed attribution. Keep the complete connection descriptor private, because it also contains the bearer.

Terminalbash
jq '{runtime_id, process_id, capability_id}' .chio/connection.json
jq '{capability_id, tool_server, tool_name,
  attribution: .metadata.attribution,
  process: .metadata.chio_process}' read-pricing.receipt.json
chio receipt verify --input read-pricing.receipt.json \
  --trusted-kernel-pubkey .chio/kernel.pub
read-pricing.receipt.json · selected fieldsjson
{
  "capability_id": "cap-01a0f85a-0bf0-72c0-8f4d-a154ff94813a",
  "tool_server": "workspace",
  "tool_name": "read_file",
  "attribution": {
    "delegation_depth": 0,
    "grant_index": 0,
    "issuer_key": "3bf50433b9c903b55d4f873cb962745fb68b2707874ea2da45ae35221007f8bb",
    "subject_key": "7f009c64ebc2d385372daa944160feb9319f29a3f7e272f709231eeacc4a3590"
  },
  "process": {
    "attempt": 1,
    "operation_key": "read-lib-v1",
    "process_id": "reviewer",
    "recovery_policy": "known_outcome_only",
    "request_sha256": "886e5f29b0694ed7c0535361e91e694d3574facf46da546e2ff8a3e157e3f6eb",
    "runtime_id": "9a416ca8-f356-4cef-bb67-f59705dfd850"
  }
}

The issuer signed the capability for its subject, and the process record binds its use to reviewer and read-pricing-v1. A process label and a subject public key are different identifiers, and the projection serves correlation: verify the complete signed receipt with the retained kernel public key.

Delegation and recovery keep the original binding

Inside a registered native connection, the kernel passes a ToolInvocationContext whose constructor is private to the kernel. This integration function reads the admitted route, subject, and capability commitment from it:

security-host/inspect_caller.rsrust
use chio_kernel::runtime::ToolInvocationContext;

#[derive(Debug)]
pub struct AdmittedCaller {
    pub request: String,
    pub server: String,
    pub tool: String,
    pub capability: String,
    pub subject: String,
    pub capability_hash: String,
}

/// Called from the registered native connection with the kernel's context.
/// Serialized arguments cannot construct ToolInvocationContext.
pub fn inspect_caller(context: &ToolInvocationContext) -> AdmittedCaller {
    AdmittedCaller {
        request: context.request_id().into(),
        server: context.server_id().into(),
        tool: context.tool_name().into(),
        capability: context.capability_id().into(),
        subject: context.subject_key().into(),
        capability_hash: context.capability_hash().into(),
    }
}

These are correlation fields the kernel selected, distinct from a transferable attestation; the serving host establishes tenant and session context separately. Keep the original authenticated context beside durable operation state, so a reconnect that copies a request ID still carries the original tenant and epoch.

A delegated capability narrows an existing chain, and its subject and target servers and tools stay consistent with the intended recipient. After restart, recovery checks the retained operation against the original context and the new serving fence, never against caller-supplied fields. For authenticated caller delivery, the configured executor signs the report, and a matching request ID is insufficient: the report also needs the original delivery, nonce, request commitment, and retained physical capture. Delegation boundaries covers the checks that keep a child inside its parent.

Trace a support session through reconnect and delegation

A support agent works for a tenant through a shared host. The tenant identifies the security partition, the agent the executing principal, the subject the authority relationship the capability represents, and the session the ongoing interaction with its retained context. The operation adds a request identity, a capability commitment, and a route, and a trusted executor may carry its own pinned identity. Matching display names bind none of these together.

Keeping them separate lets the kernel tell an exact retry from a different agent presenting a similar request, and lets a verifier tell a report from the selected executor from one signed by another valid key. Store the original commitments before later decisions depend on them; recovery loads the original authenticated record and checks current authority against it.

  • Reconnect. A dropped connection leaves the task with its principal, and a new connection to the same host proves no continuity by itself; the host's authenticated session contract decides between resuming and beginning.
  • Reauthentication. The session epoch records the change. A pending approval or captured dispatch stays bound to its original operation, and if current context cannot establish the original subject, the continuation is refused while the original state stays available to cancellation, expiry, or recovery.
  • Delegation. The child's operation identifies the child and its ancestry, which only an issued and validated child capability establishes, and the host preserves the information restrictions and shared allowances the delegated work inherits.

The swarm example refuses a task presented by the wrong worker

A planner and a writer share a mailbox and hold separate task authority. The Security swarm example provisions one credential per process and binds each call it prepares to that process. Run it in a fresh extracted directory with its required Chio commands on PATH:

Terminalbash
python3 run.py
python3 inspect.py
Output
peer: deny; chio_swarm_authority_rejected
authority: exact retry returned original; mailbox contains 2 messages

You should see the peer refused with chio_swarm_authority_rejected and two messages in the mailbox. The inspector reads the returned receipt's runtime failure code and independently counts the stopped mailbox: the wrong-worker attempt adds no message, and the planner and writer add one each. The test behind it copies the writer's exact operation and presents it with the planner's credential, and the frame below it is what the runner sends:

run.pypython
peer = swarm.invoke("triage-417", swarm.calls["fixer-417"])
original = swarm.run_planned("triage-417")
after = swarm.run_planned("fixer-417")

assert refused(peer, "chio swarm-bound runtime admission denied")
assert runtime_failure_code(peer) == "chio_swarm_authority_rejected"
assert sent_sequence(original) == "1"
assert sent_sequence(after) == "2"

frame = (json.dumps({"protocol": PROTOCOL,
                     "credential": self.credentials[process],
                     "operation": operation}, separators=(",", ":")) + "\n").encode()

swarm is the prepared host in the downloadable runner, and its invocation method sends the credential outside the operation. The process name selects a credential the host issued, which a caller cannot retag the way it could an agent_id argument, so copying the prepared payload leaves its authenticated owner behind. The run covers process and task binding on this socket host; HTTP tenant authentication and filesystem confinement are separate controls.

A mismatch is refused and kept as a rejected attempt

Compare the operation's session anchor and epoch with the current authenticated context, then the capability subject, target server and tool, and executor pin. Keep a mismatch as a rejected attempt; the original operation keeps its own record.

A negative test starts from one valid operation, keeps its request, capability, session binding, and result, and changes one identity at a time: the tenant, the authenticated agent, the session epoch, the capability digest, or the expected executor. Each substitution is refused with a binding failure, and the substituted operation neither consumes nor releases the legitimate operation's resources.

A lost response is a separate case. A matching retry may retrieve the original completion under the recovery contract, while a changed payload under the same request ID retrieves nothing that would authorize the changed effect; Replay protection covers how the kernel classifies the exact retry. Diagnostics compare commitments before anyone exports private request data: the operator learns that a session or capability digest differs, and the authorized verifier keeps the private material.

Next: narrow what the caller may do