diff --git a/agents/auth/credentials.mdx b/agents/auth/credentials.mdx
new file mode 100644
index 0000000..a412a79
--- /dev/null
+++ b/agents/auth/credentials.mdx
@@ -0,0 +1,177 @@
+---
+title: "Credentials"
+---
+
+Credentials enable fully automated authentication. Without credentials, users provide login info via the [Hosted UI](/agents/auth/hosted-ui) or [Programmatic](/agents/auth/programmatic) flow. With credentials saved, Kernel handles login automatically—both the first time and when sessions expire.
+
+## Save credentials during login
+
+Add `save_credential_as` to any invocation. The credentials entered during login are securely stored:
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+ save_credential_as: 'my-login',
+});
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+ save_credential_as="my-login",
+)
+```
+
+
+Once saved, the profile stays authenticated automatically. When the session expires, Kernel re-authenticates using the stored credentials—no user interaction needed.
+
+## Pre-store credentials
+
+For fully automated flows where no user is involved, create credentials upfront:
+
+
+```typescript TypeScript
+const credential = await kernel.credentials.create({
+ name: 'my-netflix-login',
+ domain: 'netflix.com',
+ values: {
+ email: 'user@netflix.com',
+ password: 'secretpassword123',
+ },
+});
+```
+
+```python Python
+credential = await kernel.credentials.create(
+ name="my-netflix-login",
+ domain="netflix.com",
+ values={
+ "email": "user@netflix.com",
+ "password": "secretpassword123",
+ },
+)
+```
+
+
+Then link the credential to an auth agent:
+
+
+```typescript TypeScript
+const agent = await kernel.agents.auth.create({
+ domain: 'netflix.com',
+ profile_name: 'my-profile',
+ credential_name: credential.name,
+});
+
+// Start invocation - logs in automatically using stored credentials
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+```
+
+```python Python
+agent = await kernel.agents.auth.create(
+ domain="netflix.com",
+ profile_name="my-profile",
+ credential_name=credential.name,
+)
+
+# Start invocation - logs in automatically using stored credentials
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+```
+
+
+### 2FA with TOTP
+
+For sites with authenticator app 2FA, include `totp_secret` to fully automate login:
+
+
+```typescript TypeScript
+const credential = await kernel.credentials.create({
+ name: 'my-login',
+ domain: 'github.com',
+ values: {
+ username: 'my-username',
+ password: 'my-password',
+ },
+ totp_secret: 'JBSWY3DPEHPK3PXP', // From authenticator app setup
+});
+```
+
+```python Python
+credential = await kernel.credentials.create(
+ name="my-login",
+ domain="github.com",
+ values={
+ "username": "my-username",
+ "password": "my-password",
+ },
+ totp_secret="JBSWY3DPEHPK3PXP", # From authenticator app setup
+)
+```
+
+
+### SSO / OAuth
+
+For sites with "Sign in with Google/GitHub/Microsoft", set `sso_provider` and include the OAuth provider in `allowed_domains`:
+
+
+```typescript TypeScript
+const credential = await kernel.credentials.create({
+ name: 'my-google-login',
+ domain: 'accounts.google.com',
+ sso_provider: 'google',
+ values: {
+ email: 'user@gmail.com',
+ password: 'password',
+ },
+});
+
+const agent = await kernel.agents.auth.create({
+ domain: 'target-site.com',
+ profile_name: 'my-profile',
+ credential_name: credential.name,
+ allowed_domains: ['accounts.google.com', 'google.com'],
+});
+```
+
+```python Python
+credential = await kernel.credentials.create(
+ name="my-google-login",
+ domain="accounts.google.com",
+ sso_provider="google",
+ values={
+ "email": "user@gmail.com",
+ "password": "password",
+ },
+)
+
+agent = await kernel.agents.auth.create(
+ domain="target-site.com",
+ profile_name="my-profile",
+ credential_name=credential.name,
+ allowed_domains=["accounts.google.com", "google.com"],
+)
+```
+
+
+The workflow automatically clicks the matching SSO button and completes OAuth.
+
+## Security
+
+| Feature | Description |
+|---------|-------------|
+| **Encrypted at rest** | Values encrypted using per-organization keys |
+| **Write-only** | Values cannot be retrieved via API after creation |
+| **Never logged** | Values are never written to logs |
+| **Never shared** | Values are never passed to LLMs |
+| **Isolated execution** | Authentication runs in isolated browser environments |
+
+## Notes
+
+- The `values` object is flexible—store whatever fields the login form needs (`email`, `username`, `company_id`, etc.)
+- Deleting a credential unlinks it from associated auth agents; they'll no longer auto-authenticate
+- One credential per account—create separate credentials for different user accounts
diff --git a/agents/auth/early-preview.mdx b/agents/auth/early-preview.mdx
new file mode 100644
index 0000000..ac11852
--- /dev/null
+++ b/agents/auth/early-preview.mdx
@@ -0,0 +1,56 @@
+---
+title: "Early Preview"
+description: "Agent Auth early preview documentation"
+---
+
+
+Agent Auth is now documented in the main docs. See the pages below for current documentation.
+
+
+
+
+ Introduction to Agent Auth and key concepts
+
+
+ Redirect users to complete login themselves
+
+
+ Build custom auth flows with full control
+
+
+ Store credentials for automated re-auth
+
+
+
+## Early Preview SDK Installation
+
+For early preview testers, install the preview SDK:
+
+**TypeScript/Node.js:**
+
+```json
+{
+ "dependencies": {
+ "@onkernel/sdk": "https://pkg.stainless.com/s/kernel-typescript/3f4eb01bb73f679828e195a74f41214d69c01453/dist.tar.gz"
+ }
+}
+```
+
+**Python (requirements.txt):**
+
+```
+kernel @ https://pkg.stainless.com/s/kernel-python/e941d0fb0a62cb8a1aad2424577c825bd6764df4/kernel-0.24.0-py3-none-any.whl
+```
+
+Or in pyproject.toml:
+
+```toml
+[project]
+dependencies = [
+ "kernel @ https://pkg.stainless.com/s/kernel-python/e941d0fb0a62cb8a1aad2424577c825bd6764df4/kernel-0.24.0-py3-none-any.whl",
+]
+```
+
+## Support
+
+Questions or issues? Reach out to us on Slack!
diff --git a/agents/auth/hosted-ui.mdx b/agents/auth/hosted-ui.mdx
new file mode 100644
index 0000000..b12782e
--- /dev/null
+++ b/agents/auth/hosted-ui.mdx
@@ -0,0 +1,225 @@
+---
+title: "Hosted UI"
+description: "The simplest way to create authenticated browser sessions"
+---
+
+Collect credentials via Kernel's hosted page, then use the authenticated session in your automations. This is the recommended approach for most applications.
+
+Use the Hosted UI when:
+- You need users to provide their credentials
+- You want the simplest integration with minimal code
+- You want Kernel to handle 2FA and multi-step login flows
+
+## Getting started
+
+### 1. Create an Auth Agent
+
+An Auth Agent represents a login session for a specific website and profile.
+
+
+```typescript TypeScript
+const agent = await kernel.agents.auth.create({
+ domain: 'linkedin.com',
+ profile_name: 'linkedin-profile',
+});
+```
+
+```python Python
+agent = await kernel.agents.auth.create(
+ domain="linkedin.com",
+ profile_name="linkedin-profile",
+)
+```
+
+
+### 2. Start Authentication
+
+Create an invocation to get the hosted login URL.
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+```
+
+
+To enable automatic re-authentication when sessions expire, add `save_credential_as`:
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+ save_credential_as: 'my-saved-creds',
+});
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+ save_credential_as="my-saved-creds",
+)
+```
+
+
+### 3. Collect Credentials
+
+Send the user to the hosted login page:
+
+
+```typescript TypeScript
+window.location.href = invocation.hosted_url;
+```
+
+```python Python
+# Return the URL to your frontend
+print(f"Redirect to: {invocation.hosted_url}")
+```
+
+
+The user will:
+1. See the login page for the target website
+2. Enter their credentials
+3. Complete 2FA if needed
+
+### 4. Poll for Completion
+
+On your backend, poll until authentication completes:
+
+
+```typescript TypeScript
+let state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+
+while (state.status === 'IN_PROGRESS') {
+ await new Promise(r => setTimeout(r, 2000));
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+}
+
+if (state.status === 'SUCCESS') {
+ console.log('Authentication successful!');
+}
+```
+
+```python Python
+state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+while state.status == "IN_PROGRESS":
+ await asyncio.sleep(2)
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+if state.status == "SUCCESS":
+ print("Authentication successful!")
+```
+
+
+
+Poll every 2 seconds. The session expires after 5 minutes if not completed.
+
+
+### 5. Use the Profile
+
+Create browsers with the profile—they're already logged in:
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ profile: { name: 'linkedin-profile' },
+ stealth: true,
+});
+// Browser is logged into LinkedIn!
+```
+
+```python Python
+browser = await kernel.browsers.create(
+ profile={"name": "linkedin-profile"},
+ stealth=True,
+)
+# Browser is logged into LinkedIn!
+```
+
+
+
+Use `stealth: true` when creating browsers for authenticated sessions.
+
+
+
+## Complete Example
+
+
+```typescript TypeScript
+import Kernel from '@onkernel/sdk';
+
+const kernel = new Kernel();
+
+// Create auth agent
+const agent = await kernel.agents.auth.create({
+ domain: 'doordash.com',
+ profile_name: 'doordash-user-123',
+});
+
+// Start authentication
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+
+// Send user to hosted page
+console.log('Login URL:', invocation.hosted_url);
+
+// Poll for completion
+let state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+while (state.status === 'IN_PROGRESS') {
+ await new Promise(r => setTimeout(r, 2000));
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+}
+
+if (state.status === 'SUCCESS') {
+ // Use the profile
+ const browser = await kernel.browsers.create({
+ profile: { name: 'doordash-user-123' },
+ stealth: true,
+ });
+ // Browser is logged into DoorDash!
+}
+```
+
+```python Python
+from kernel import Kernel
+import asyncio
+
+kernel = Kernel()
+
+# Create auth agent
+agent = await kernel.agents.auth.create(
+ domain="doordash.com",
+ profile_name="doordash-user-123",
+)
+
+# Start authentication
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+
+# Send user to hosted page
+print(f"Login URL: {invocation.hosted_url}")
+
+# Poll for completion
+state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+while state.status == "IN_PROGRESS":
+ await asyncio.sleep(2)
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+if state.status == "SUCCESS":
+ # Use the profile
+ browser = await kernel.browsers.create(
+ profile={"name": "doordash-user-123"},
+ stealth=True,
+ )
+ # Browser is logged into DoorDash!
+```
+
diff --git a/agents/auth/overview.mdx b/agents/auth/overview.mdx
new file mode 100644
index 0000000..2e24304
--- /dev/null
+++ b/agents/auth/overview.mdx
@@ -0,0 +1,130 @@
+---
+title: "Overview"
+description: "Create authenticated browser sessions for your automations"
+---
+
+Agent Auth creates authenticated browser sessions for your automations—handling login flows, 2FA/OTP, and session persistence automatically.
+
+## How It Works
+
+
+
+ An **Auth Agent** represents a login session for a specific website and profile. Create one for each domain + profile combination.
+
+
+```typescript TypeScript
+const agent = await kernel.agents.auth.create({
+ domain: 'netflix.com',
+ profile_name: 'netflix-user-123',
+});
+```
+
+```python Python
+agent = await kernel.agents.auth.create(
+ domain="netflix.com",
+ profile_name="netflix-user-123",
+)
+```
+
+
+
+ Start the login flow. Users provide credentials via the hosted page (or your own UI).
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+
+// Send user to login page
+console.log('Login URL:', invocation.hosted_url);
+
+// Poll until complete
+let state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+while (state.status === 'IN_PROGRESS') {
+ await new Promise(r => setTimeout(r, 2000));
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+}
+
+if (state.status === 'SUCCESS') {
+ console.log('Authenticated!');
+}
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+
+# Send user to login page
+print(f"Login URL: {invocation.hosted_url}")
+
+# Poll until complete
+state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+while state.status == "IN_PROGRESS":
+ await asyncio.sleep(2)
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+if state.status == "SUCCESS":
+ print("Authenticated!")
+```
+
+
+
+ Create browsers with the saved profile—they're already logged in.
+
+
+```typescript TypeScript
+const browser = await kernel.browsers.create({
+ profile: { name: 'netflix-user-123' },
+ stealth: true,
+});
+// Browser is logged into Netflix!
+```
+
+```python Python
+browser = await kernel.browsers.create(
+ profile={"name": "netflix-user-123"},
+ stealth=True,
+)
+# Browser is logged into Netflix!
+```
+
+
+ For fully automated flows, link [Credentials](/agents/auth/credentials) to enable re-authentication without user input.
+
+
+
+## Choose Your Integration
+
+
+
+ **Best for:** Most applications
+
+ Collect credentials via Kernel's hosted page. Simplest integration.
+
+
+ **Best for:** Custom UI or headless
+
+ Build your own credential collection with polling and submit APIs.
+
+
+
+## Why Agent Auth?
+
+The most valuable workflows live behind logins. Agent Auth provides:
+
+- **Works on any website** - Login pages are handled automatically
+- **SSO/OAuth support** - "Sign in with Google/GitHub/Microsoft" buttons work out of the box
+- **2FA/OTP handling** - TOTP codes automated, SMS/email OTP via the UI
+- **Session monitoring** - Automatic re-authentication when sessions expire
+- **Secure by default** - Credentials encrypted at rest, never exposed in API responses
+
+## Security
+
+| Feature | Description |
+|---------|-------------|
+| **Encrypted credentials** | Values encrypted with per-organization keys |
+| **No credential exposure** | Never returned in API responses or passed to LLMs |
+| **Encrypted profiles** | Browser session state encrypted end-to-end |
+| **Isolated execution** | Each login runs in an isolated browser environment |
diff --git a/agents/auth/programmatic.mdx b/agents/auth/programmatic.mdx
new file mode 100644
index 0000000..9c4922a
--- /dev/null
+++ b/agents/auth/programmatic.mdx
@@ -0,0 +1,245 @@
+---
+title: "Programmatic Flow"
+description: "Build your own credential collection UI with full control"
+---
+
+Build your own credential collection UI instead of using the hosted page. Poll for login fields, then submit credentials via the API.
+
+Use the Programmatic flow when:
+- You need a custom credential collection UI that matches your app's design
+- You're building headless/automated authentication
+- You have credentials stored and want to authenticate without user interaction
+
+## How It Works
+
+
+
+ Same as [Hosted UI](/agents/auth/hosted-ui)
+
+
+ Poll until `step` becomes `awaiting_input`, then submit credentials
+
+
+ If more fields appear (2FA code), submit again—same loop handles it
+
+
+
+## Getting started
+
+### 1. Create an Auth Agent
+
+
+```typescript TypeScript
+const agent = await kernel.agents.auth.create({
+ domain: 'github.com',
+ profile_name: 'github-profile',
+});
+```
+
+```python Python
+agent = await kernel.agents.auth.create(
+ domain="github.com",
+ profile_name="github-profile",
+)
+```
+
+
+### 2. Start Authentication
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+```
+
+
+To save credentials for automatic re-authentication:
+
+
+```typescript TypeScript
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+ save_credential_as: 'my-saved-creds',
+});
+```
+
+```python Python
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+ save_credential_as="my-saved-creds",
+)
+```
+
+
+### 3. Poll and Submit Credentials
+
+A single loop handles everything—initial login, 2FA, and completion:
+
+
+```typescript TypeScript
+let state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+
+while (state.status === 'IN_PROGRESS') {
+ // Submit when fields are ready (login or 2FA)
+ if (state.step === 'awaiting_input' && state.pending_fields?.length) {
+ const fieldValues = getCredentialsForFields(state.pending_fields);
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ { field_values: fieldValues }
+ );
+ }
+
+ await new Promise(r => setTimeout(r, 2000));
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+}
+
+if (state.status === 'SUCCESS') {
+ console.log('Authentication successful!');
+}
+```
+
+```python Python
+state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+while state.status == "IN_PROGRESS":
+ # Submit when fields are ready (login or 2FA)
+ if state.step == "awaiting_input" and state.pending_fields:
+ field_values = get_credentials_for_fields(state.pending_fields)
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ field_values=field_values,
+ )
+
+ await asyncio.sleep(2)
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+if state.status == "SUCCESS":
+ print("Authentication successful!")
+```
+
+
+The `pending_fields` array tells you what the login form needs:
+
+```typescript
+// Example pending_fields for login
+[{ name: 'username', type: 'text' }, { name: 'password', type: 'password' }]
+
+// Example pending_fields for 2FA
+[{ name: 'otp', type: 'code' }]
+```
+
+## Complete Example
+
+
+```typescript TypeScript
+import Kernel from '@onkernel/sdk';
+
+const kernel = new Kernel();
+
+// Create auth agent
+const agent = await kernel.agents.auth.create({
+ domain: 'github.com',
+ profile_name: 'github-profile',
+});
+
+const invocation = await kernel.agents.auth.invocations.create({
+ auth_agent_id: agent.id,
+});
+
+// Single polling loop handles login + 2FA
+let state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+
+while (state.status === 'IN_PROGRESS') {
+ if (state.step === 'awaiting_input' && state.pending_fields?.length) {
+ // Check what fields are needed
+ const fieldNames = state.pending_fields.map(f => f.name);
+
+ if (fieldNames.includes('username')) {
+ // Initial login
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ { field_values: { username: 'my-username', password: 'my-password' } }
+ );
+ } else {
+ // 2FA or additional fields
+ const code = await promptUserForCode();
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ { field_values: { [state.pending_fields[0].name]: code } }
+ );
+ }
+ }
+
+ await new Promise(r => setTimeout(r, 2000));
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id);
+}
+
+if (state.status === 'SUCCESS') {
+ console.log('Authentication successful!');
+
+ const browser = await kernel.browsers.create({
+ profile: { name: 'github-profile' },
+ stealth: true,
+ });
+ // Browser is logged into GitHub!
+}
+```
+
+```python Python
+from kernel import Kernel
+import asyncio
+
+kernel = Kernel()
+
+# Create auth agent
+agent = await kernel.agents.auth.create(
+ domain="github.com",
+ profile_name="github-profile",
+)
+
+invocation = await kernel.agents.auth.invocations.create(
+ auth_agent_id=agent.id,
+)
+
+# Single polling loop handles login + 2FA
+state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+while state.status == "IN_PROGRESS":
+ if state.step == "awaiting_input" and state.pending_fields:
+ # Check what fields are needed
+ field_names = [f["name"] for f in state.pending_fields]
+
+ if "username" in field_names:
+ # Initial login
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ field_values={"username": "my-username", "password": "my-password"},
+ )
+ else:
+ # 2FA or additional fields
+ code = input("Enter code: ")
+ await kernel.agents.auth.invocations.submit(
+ invocation.invocation_id,
+ field_values={state.pending_fields[0]["name"]: code},
+ )
+
+ await asyncio.sleep(2)
+ state = await kernel.agents.auth.invocations.retrieve(invocation.invocation_id)
+
+if state.status == "SUCCESS":
+ print("Authentication successful!")
+
+ browser = await kernel.browsers.create(
+ profile={"name": "github-profile"},
+ stealth=True,
+ )
+ # Browser is logged into GitHub!
+```
+
diff --git a/docs.json b/docs.json
index dedd1b8..6b01c22 100644
--- a/docs.json
+++ b/docs.json
@@ -98,6 +98,15 @@
"browsers/faq"
]
},
+ {
+ "group": "Agent Auth",
+ "pages": [
+ "agents/auth/overview",
+ "agents/auth/hosted-ui",
+ "agents/auth/programmatic",
+ "agents/auth/credentials"
+ ]
+ },
{
"group": "App Platform",
"pages": [
@@ -182,7 +191,8 @@
"reference/cli/create",
"reference/cli/auth",
"reference/cli/browsers",
- "reference/cli/apps"
+ "reference/cli/apps",
+ "reference/cli/mcp"
]
},
{
diff --git a/style.css b/style.css
index 003ae24..58e1f59 100644
--- a/style.css
+++ b/style.css
@@ -29,7 +29,7 @@ table {
overflow: hidden !important;
border: 1px solid rgb(var(--gray-950) / 0.1) !important;
background-color: rgb(249, 250, 251) !important;
- margin: 1.25rem 0 !important;
+ margin: 0 !important;
display: table !important;
}