Tokens
Tokens represent user state and permissions. They're signed JWTs that prove things like "this user is logged in" or "this user has connected their Gmail account."
What Tokens Do
Tokens solve two problems:
State - The AI needs to know what context a user is in. Are they logged in? Have they selected a project? Connected an external account?
Permissions - Tools need to know what a user is allowed to do. Can they access this data? Call this API?
Instead of building complex permission systems, you define tokens declaratively. The architecture handles verification and enforcement.
Defining Tokens
Tokens are defined in tokens.json:
{
"account": {
"state": "Signed in",
"description": "User authentication token",
"schema": {
"type": "object",
"properties": {
"accountId": {
"type": "string",
"description": "User's account ID"
},
"email": {
"type": "string",
"description": "User's email address"
}
},
"required": ["accountId", "email"]
},
"expiresIn": 2592000000
}
}
Token Fields
| Field | Required | What It Does |
|---|---|---|
state |
No | Human-readable state shown to AI when user has this token |
description |
No | Explains what this token represents |
schema |
Yes | JSON Schema for the token's payload |
expiresIn |
No | How long until expiration (milliseconds, default: 24 hours) |
The State Field
The state field is important. It tells the AI what having this token means.
When a user has an account token with "state": "Signed in", the AI sees:
**User context:**
- Signed in (email: "[email protected]")
This helps the AI avoid unnecessary actions. If the user is already signed in, it won't try to log them in again.
Creating Tokens
Tools with the token capability can create tokens:
// src/tools/login.ts
export default async function login(
input: Input,
capabilities: Capabilities
): Promise<Output> {
// Verify credentials...
const token = await capabilities.token.sign('account', {
accountId: user.id,
email: user.email
});
return { success: true };
}
The sign method creates a cryptographically signed JWT. The token is returned to the client and stored for future requests.
Requiring Tokens
Tools declare which tokens they need in tools.json:
{
"name": "list_notes",
"input_tokens": {
"@my-org/auth": {
"required": ["account"]
}
}
}
This tool only works if the user has an account token from @my-org/auth. If they don't, the orchestrator won't include this tool in its planning.
Cross-App Tokens
Tokens can be required across apps. An email app might need auth from a separate auth app:
{
"name": "send_email",
"input_tokens": {
"@my-org/auth": {
"required": ["account"]
},
"@my-org/email": {
"required": ["email_access"]
}
}
}
This creates a clear dependency chain. The orchestrator knows that to send email, the user needs to:
- Be logged in (have
accounttoken) - Have connected their email (have
email_accesstoken)
Token Expiration
Tokens expire. The expiresIn field controls this:
{
"session": {
"expiresIn": 86400000
}
}
This token expires after 24 hours (in milliseconds).
Handling Expired Tokens
Sometimes you want tools to work with expired tokens. This is useful when the underlying credential (like an OAuth refresh token) is still valid:
{
"name": "list_emails",
"input_tokens": {
"@my-org/gmail": {
"required": ["gmail_access"],
"allow_expired": ["gmail_access"]
}
}
}
With allow_expired, the tool receives the expired token and can refresh it internally:
export default async function list_emails(
input: Input,
capabilities: Capabilities
): Promise<Output> {
// Check if token is expired
if (isExpired(capabilities.token.current)) {
// Use refresh token to get new access
await refreshOAuthToken();
// Issue new capability token
await capabilities.token.sign('gmail_access', {
accountId: '...',
email: '...'
});
}
// Continue with listing emails...
}
This creates a seamless experience - users don't need to re-authenticate when tokens expire.
Token Payload in Storage Paths
Token fields can be used in storage paths. This is how you create user-specific or team-specific storage:
{
"same_app": {
"/users/<token.accountId>/notes/": {
"operations": ["read", "write"],
"tokenType": "account",
"tokenFromApp": "@my-org/auth"
}
}
}
The <token.accountId> gets replaced with the actual value from the user's token, ensuring each user can only access their own data.
Security
Tokens are signed using Ed25519 cryptography. This means:
- Tokens can't be forged or modified
- Any service can verify a token using public keys
- Key rotation happens automatically
The token service handles all the cryptographic details. You just define schemas and call sign.