Skip to content

Latest commit

 

History

History
570 lines (490 loc) · 33.8 KB

File metadata and controls

570 lines (490 loc) · 33.8 KB

AgentBox — Architecture

Current Architecture (Phase 1)

┌─────────────────────────────────────────────────┐
│                  AgentBox Container               │
│                                                   │
│  ┌──────────┐   nginx (:80)                      │
│  │  nginx   │──────┬──────────────────────────── │
│  └──────────┘      │                              │
│                    ├── /           → code-server  │
│                    └── /terminal/ → ttyd          │
│                                                   │
│  ┌──────────────┐  ┌──────────┐                  │
│  │ code-server  │  │   ttyd   │                  │
│  │  :8080       │  │  :7681   │                  │
│  │ (127.0.0.1)  │  │(127.0.0.1)│                 │
│  └──────────────┘  └──────────┘                  │
│                                                   │
│  Tools: copilot, playwright-cli, az, gh, python  │
│  User:  agentbox (passwordless sudo)             │
│  Shell: zsh (Oh My Zsh)                          │
│  Workspace: /home/agentbox/workspace             │
└─────────────────────────────────────────────────┘

Container Image Layers

ubuntu:24.04
├── Base: curl, wget, git, zsh, vim, nano, jq, ripgrep, python3, nginx
├── Node.js 22 (via nodesource)
├── Azure CLI + azure-devops extension
├── GitHub CLI (gh)
├── @github/copilot (npm global)
├── @playwright/cli + Chromium
├── ttyd (web terminal binary)
├── code-server (VS Code in browser)
├── agentbox user + oh-my-zsh
└── nginx reverse proxy config + entrypoint.sh

Port Architecture

Service Internal Port External (Local Docker) External (ACI)
nginx 80 Mapped (random) 80 (direct)
code-server 8080 Mapped (random) via nginx
ttyd 7681 Mapped (random) via nginx
sshd 22 Mapped (random) 22 (direct)

Local Docker exposes all three ports (direct access + proxy). ACI exposes only port 80 (nginx proxy handles routing).


Target Architecture (Phase 2+)

┌──────────────────────────────────────────────────────────────────┐
│                           Clients                                 │
│  iPhone Safari │ Desktop Browser │ VS Code Remote │ SSH           │
└────┬─────────────────┬──────────────────┬──────────────┬─────────┘
     │ HTTPS            │ HTTPS            │ HTTPS         │ SSH 2222
     │ (dashboard)      │ (*.box subdom)   │ (*.box)       │ (direct)
     │                  │                  │               │
┌────┴──────────────────┴──────────────────┴───────┐       │
│              Cloudflare (TLS edge)                │       │
│  agentbox.networg.com + *.agentbox.networg.com    │       │
│  Free wildcard TLS + DDoS protection              │       │
└────┬──────────────────────────────┬──────────────┘       │
     │                              │                       │
     │ agentbox.networg.com         │ *.agentbox.networg.com│
     ▼                              ▼                       │
┌────────────────────┐  ┌──────────────────────────────┐    │
│ Azure Static Web   │  │ API (Container Apps)              │    │
│ App (Free tier)    │  │ ASP.NET Core                  │    │
│                    │  │                               │    │
│ React SPA (Vite)   │  │ ├── /api/* REST controllers   │    │
│ - Dashboard        │  │ │   POST/GET/DELETE /instances │    │
│ - Spawn form       │  │ │   OAuth callbacks           │    │
│ - OAuth flows      │  │ │   SSH cert signing          │    │
│ - Token vending    │  │ │   Copilot token check       │    │
│ - Mobile-first     │  │ │                             │    │
│                    │  │ ├── YARP reverse proxy         │    │
│ Built-in auth:     │  │ │   *.agentbox → ACI fleet    │    │
│ Entra ID (SWA)     │  │ │   Dynamic routes            │    │
│                    │  │ │   WebSocket ✅               │    │
│ CDN: global edge   │  │ │                             │    │
│ Custom domain ✅   │  │ └── Easy Auth (Entra ID)      │    │
│                    │  │                               │    │
│ API calls:         │  │ Data: Azure Table Storage     │    │
│ → api.*            │  │ Secrets: Key Vault            │    │
└────────────────────┘  └──────────────┬───────────────┘    │
                                       │ Azure SDK          │
                           ┌───────────┼───────────┐        │
                           │           │           │        │
                 ┌─────────┴──┐ ┌─────┴──────┐ ┌──┴──────────┐
                 │ AgentBox 1  │ │ AgentBox 2  │ │ AgentBox N  │ ← SSH
                 │ (ACI)       │ │ (ACI)       │ │ (ACI)       │
                 │ nginx :80   │ │ nginx :80   │ │ nginx :80   │
                 │ sshd :2222  │ │ sshd :2222  │ │ sshd :2222  │
                 │ sys-MI      │ │ sys-MI      │ │ sys-MI      │
                 └─────────────┘ └─────────────┘ └─────────────┘

Dashboard Flow (SWA)

  1. User opens https://agentbox.networg.com
  2. Cloudflare terminates TLS, forwards to Azure Static Web App
  3. SWA built-in auth: Entra ID login (networg.com only)
  4. React SPA loads — dashboard, spawn form, token vending
  5. API calls go to https://api.agentbox.networg.com/api/*

Container Access Flow (YARP)

  1. User requests https://3448.agentbox.networg.com
  2. Cloudflare terminates TLS (wildcard cert), proxies to Container Apps
  3. Easy Auth validates Entra ID session on Container App
  4. YARP extracts subdomain (3448), routes to ACI container agentbox-3448
  5. ACI nginx routes: / → code-server, /terminal/ → ttyd

API Flow

  1. SWA React app sends POST /api/instances (no name — server assigns sequential 4-digit ID)
  2. API assigns next ID (e.g., 3448), creates ACI container via Azure SDK, injects scoped tokens
  3. YARP config updated in-process with new route
  4. Returns container URL: https://3448.agentbox.networg.com

Box Naming

Boxes are assigned sequential 4-digit IDs (e.g., 0001, 3448). The counter is stored in Azure Table Storage (PartitionKey="system", RowKey="counter") with optimistic concurrency. URLs are {id}.agentbox.networg.com.

Local Run Flow (Dashboard as Token Vending Machine)

When users need local disk access (e.g., working on a local project), cloud containers won't help. The dashboard provides a frictionless local experience:

┌─────────────────────────────────────────────────────────────────┐
│  1. User visits agentbox.networg.com                            │
│  2. Signs in with Entra ID (networg.com)                        │
│  3. GitHub App OAuth → dashboard has ghu_* token                │
│  4. User clicks "Run Locally" tab                               │
│  5. Dashboard renders ready-to-copy command:                    │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ docker run -it --rm \                                    │    │
│  │   -v $(pwd):/home/agentbox/workspace \                   │    │
│  │   -p 8080:80 \                                           │    │
│  │   -e GH_TOKEN=ghu_xxxxxxxxxxxx \                         │    │
│  │   -e COPILOT_GITHUB_TOKEN=ghu_xxxxxxxxxxxx \             │    │
│  │   ghcr.io/networg/agentbox:latest                        │ 📋│
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
│  6. User copies, pastes in terminal, runs from project dir      │
│  7. Opens http://localhost:8080 for VS Code with local files    │
│  8. Copilot CLI is pre-authenticated via COPILOT_GITHUB_TOKEN   │
└─────────────────────────────────────────────────────────────────┘

Key: the user never creates or manages tokens. The dashboard handles all OAuth flows and presents a one-click copy command.


Identity & Security Model

Authentication Flow

All users authenticate via Microsoft Entra ID (networg.com tenant). The fleet management website handles all OAuth flows — users never manage tokens directly.

┌─────────────────────────────────────────────────────────────────┐
│                   Authentication Flow                            │
│                                                                  │
│  1. User visits dashboard (agentbox.networg.com)                │
│  2. Dashboard redirects to Entra ID login (networg.com tenant)  │
│  3. User signs in with work account (user@networg.com)          │
│  4. Dashboard receives Entra ID token (id_token + access_token) │
│  5. User clicks "Spawn AgentBox" — four auth flows triggered:    │
│     a. GitHub App OAuth:                                        │
│        → github.com/login/oauth/authorize                       │
│        → User authorizes → ghu_* token returned                 │
│     b. Azure DevOps OAuth:                                      │
│        → app.vssps.visualstudio.com/oauth2/authorize            │
│        → scope=vso.code_write vso.work                          │
│        → User authorizes → ADO token returned                   │
│     c. Atlassian OAuth 2.0 (3LO):                              │
│        → auth.atlassian.com/authorize                           │
│        → scope=read:servicedesk-request write:servicedesk-req.. │
│        → User authorizes → JSM token returned                   │
│     d. Dataverse (per-container managed identity):              │
│        → User selects environments + security role per env     │
│        → No user OAuth needed — provisioned during container   │
│          creation (system-assigned MI + app user + role)       │
│  6. Dashboard calls API: POST /api/instances                     │
│     → API creates ACI container with secure env vars:           │
│        GH_TOKEN=ghu_*  (Copilot CLI + GitHub)                  │
│        AZURE_DEVOPS_EXT_PAT=xxx  (az devops — scoped as user)  │
│        JIRA_TOKEN=xxx  (JSM read tickets + internal notes)      │
│     → Container fetches Dataverse tokens via az login --identity│
│     → Sets TTL=5 days, owner=user@networg.com                  │
│  7. Container starts:                                           │
│     → gh auth login --with-token (GH_TOKEN)                    │
│     → AZURE_DEVOPS_EXT_PAT auto-used by az devops commands     │
│     → JIRA_TOKEN available for JSM REST API calls               │
│     → Dataverse tokens fetched via GET /api/tokens/dataverse/*  │
│     → All five services pre-authenticated                       │
│  8. User accesses AgentBox via HTTPS URL                        │
│     → AI can code, PR, read work items, read JSM tickets,      │
│       post internal notes, read/write Dataverse records         │
│     → GitHub/ADO/JSM: user's identity, scoped permissions      │
│     → Dataverse: app identity, custom "AgentBox AI Agent" role  │
│     → Uses developer's existing licenses — zero extras          │
└─────────────────────────────────────────────────────────────────┘

Token & Identity Management

Credential Source Lifetime Purpose Scope Limitation
Entra ID token Microsoft login ~1hr (refresh) Dashboard/API auth networg.com tenant
GitHub user access token GitHub App OAuth 8hr (refreshable) git + gh CLI (+ Copilot if own seat) GitHub repos user can access
GitHub refresh token GitHub App OAuth 6 months Refresh ghu_* token
Shared Copilot token Shared account OAuth 8hr (auto-refreshed by API) Copilot CLI for unlicensed users Copilot auth only, no repo access
Azure DevOps OAuth token ADO OAuth flow 1hr (refreshable) az devops commands vso.code_write + vso.work only
ADO refresh token ADO OAuth flow 1 year Refresh ADO token
Jira/JSM OAuth token Atlassian 3LO 1hr (refreshable) JSM API calls read:servicedesk-request + write:servicedesk-request only
Jira refresh token Atlassian 3LO 90 days Refresh Jira token
Dataverse access token System-assigned managed identity ~1hr (re-acquire via az) Dataverse Web API Security role selected at spawn time per environment
SharePoint access token System-assigned MI (or shared reader app) ~1hr (re-acquire via az) Graph API — read documents Sites.Selected, only sites granted at spawn

Scoped Impersonation

All three service tokens (GitHub, ADO, JSM) act as the user — using their existing license and project access — but are strictly scoped by the OAuth permissions our apps request. Copilot CLI uses either the user's own token or a shared account token (8hr-lived, server-refreshed).

Azure DevOps — Scoped Impersonation (Zero Extra Licenses)

Instead of per-developer managed identities (which would double ADO license count), we use Azure DevOps OAuth with strictly scoped permissions. The token acts as the user (inherits their project access, uses their existing license) but is limited to specific vso.* scopes.

┌─── What the AI CAN do ─────────────────────────────────────────┐
│                                                                 │
│  GitHub (ghu_* token — user identity):                         │
│  ✅ Read/write code, create PRs, manage reviews                │
│  ✅ Run Copilot CLI (own token or shared account token)        │
│                                                                 │
│  Azure DevOps (vso.code_write + vso.work — user identity):     │
│  ✅ Read/write source code (git clone, push)                   │
│  ✅ Create and manage pull requests and code reviews            │
│  ✅ Read work items (for context)                              │
│                                                                 │
│  Jira Service Management (read + write:servicedesk-request     │
│                            — user identity):                    │
│  ✅ Read customer requests/tickets                             │
│  ✅ Post internal notes to tickets ("public": false)           │
│                                                                 │
│  Dataverse / Power Platform (per-container managed identity):  │
│  ✅ Read entity metadata and schema                            │
│  ✅ Read/create/update records (per selected security role)    │
│  ✅ Execute custom actions and workflows                       │
│  ✅ Permissions determined by the security role the user       │
│     selected at spawn time — NOT the user's own role           │
│                                                                 │
│  SharePoint Online (Sites.Selected — per-container MI or       │
│                      shared reader app):                       │
│  ✅ Read documents from explicitly selected sites              │
│  ✅ List files, download content, read metadata                │
│                                                                 │
│  GitHub/ADO/JSM: scoped to projects the developer has access to│
│  Dataverse: scoped to selected environments + custom role      │
│  SharePoint: scoped to selected sites only (read-only)         │
└─────────────────────────────────────────────────────────────────┘

┌─── What the AI CANNOT do ─────────────────────────────────────┐
│                                                                 │
│  GitHub (scopes not requested):                                │
│  ❌ Change repo settings, manage teams, access secrets         │
│  ❌ Trigger or re-run GitHub Actions workflows                 │
│  ❌ Modify .github/workflows files                             │
│  ❌ Access other orgs or repos outside app installation scope  │
│                                                                 │
│  Azure DevOps (scopes not requested):                          │
│  ❌ Run or manage pipelines/builds                             │
│  ❌ Create or delete repositories                              │
│  ❌ Manage releases/deployments                                │
│  ❌ Change project settings or permissions                     │
│  ❌ Access service connections or secrets                      │
│  ❌ Modify work items                                          │
│                                                                 │
│  Jira Service Management:                                      │
│  ❌ Transition tickets (change status)                         │
│  ❌ Manage service desks or request types                      │
│  ❌ Manage customers or organizations                          │
│  ❌ Access Confluence, Bitbucket, or other Atlassian products  │
│                                                                 │
│  Dataverse (per-container identity — user-selected role):      │
│  ❌ Anything beyond the security role assigned at spawn time   │
│  ❌ Different role = different permissions per AgentBox        │
│  ❌ E.g. "Basic User" role: no delete, no admin, no settings  │
│                                                                 │
│  SharePoint Online:                                            │
│  ❌ Write, upload, or modify any documents                     │
│  ❌ Access sites not explicitly selected at spawn time         │
│  ❌ Access user's full SharePoint (delegated Sites.Read.All)   │
│                                                                 │
│  ❌ Anything administrative across any service                  │
└─────────────────────────────────────────────────────────────────┘

License impact: ZERO — the token uses the developer's existing ADO license/seat.

Per-Developer Managed Identity (Azure DevOps)

Each developer gets their own user-assigned managed identity in Azure. This is the key to per-developer ADO project isolation.

Container Lifecycle & TTL

┌─── Container Lifecycle ────────────────────────────────────────┐
│                                                                 │
│  SPAWN ──→ RUNNING ──→ TTL EXPIRED (5 days max) ──→ DESTROYED │
│                 │                                               │
│                 ├──→ USER DESTROYS ──→ DESTROYED               │
│                 │                                               │
│                 └──→ IDLE TIMEOUT (configurable) ──→ DESTROYED │
│                                                                 │
│  Metadata tracked: owner (Entra UPN), created_at, ttl,         │
│                    github_user, managed_identity_id,            │
│                    dataverse_environments (env→role map),       │
│                    shared_with (list of UPNs),                  │
│                    container_name                               │
└─────────────────────────────────────────────────────────────────┘
  • Max TTL: 5 days from creation
  • Idle timeout: configurable (e.g., 8 hours no activity)
  • Cleanup job: Azure Function on timer trigger, checks TTL, destroys expired containers
  • Owner tagging: each ACI container tagged with owner=user@networg.com

Network Security

┌─── Network Model ──────────────────────────────────────────────┐
│                                                                 │
│  Internet ──→ Container Apps (Easy Auth + Caddy HTTPS proxy)   │
│                    │                                            │
│                    ├── Entra ID authentication (networg.com)    │
│                    ├── forward_auth → AgentBox API              │
│                    │   (ownership + shared access + admin check)│
│                    └── Proxy to ACI container (:80/nginx)       │
│                                                                 │
│  Authorization rules (checked per request via forward_auth):    │
│  ✅ Container owner (user@networg.com == container owner tag)  │
│  ✅ Shared access (owner granted access to team member)        │
│  ✅ Admin (member of "AgentBox Admins" Entra ID group)         │
│  ❌ Everyone else → 403 Forbidden                              │
│                                                                 │
│  Container outbound (future agent firewall):                    │
│  ALLOW: github.com, npm, pypi, azure.com, dev.azure.com       │
│  ALLOW: Custom domains per container                            │
│  DENY: everything else (configurable)                           │
│                                                                 │
│  Local Docker: no auth (localhost only, user's own machine)     │
└─────────────────────────────────────────────────────────────────┘

Container Access Security (HTTPS Proxy Layer)

All container access is secured at the proxy layer — no changes needed to container images:

User → https://my-project.agentbox.networg.com
  │
  ├── 1. Container Apps Easy Auth
  │      → Entra ID login (networg.com tenant only)
  │      → Sets X-MS-CLIENT-PRINCIPAL header (user identity)
  │
  ├── 2. Caddy forward_auth → GET /api/authorize
  │      → API receives: user UPN + requested container name
  │      → Checks: owner? shared? admin?
  │      → Returns 200 (allow) or 403 (deny)
  │
  └── 3. If authorized: Caddy proxies to ACI container
         → code-server, ttyd, future HTTP services
         → WebSocket pass-through (code-server + ttyd need it)
  • Authentication: Entra ID via Container Apps Easy Auth (cookie-based, SSO)
  • Authorization: Per-container ownership + shared access list + admin override
  • Session: After Entra ID login, session cookie provides seamless access
  • No container changes: Auth handled entirely at proxy — containers stay unauthenticated internally
  • Admin access: Members of "AgentBox Admins" Entra ID group can access any container
  • Shared access: Container owner can grant access to specific team members (stored in container metadata)

SSH Access Security (Certificate-Based Authentication)

SSH provides native terminal access, VS Code Remote SSH, and SCP file transfers — critical for desktop power users.

Approach: Entra ID SSH Certificate Authentication (preferred) or Custom CA fallback

Microsoft Entra ID provides a built-in SSH Certificate Authority that issues short-lived OpenSSH certificates. This is the Azure-native approach for SSH authentication.

Option A: Entra ID as SSH CA (preferred — investigate for ACI)

az ssh vm --name my-project --resource-group agentbox-containers
  │
  ├── 1. az CLI authenticates to Entra ID (user's existing session)
  │
  ├── 2. Entra ID issues short-lived SSH certificate
  │      → Signed by Microsoft's SSH CA
  │      → Bound to user's Entra ID identity
  │      → Short-lived (hours)
  │
  ├── 3. az CLI connects to container via SSH using the certificate
  │
  └── 4. Container sshd validates certificate against Entra ID CA public key
         → AuthorizedPrincipalsCommand checks container access (owner/shared/admin)

How it works natively (Azure VMs):

  • AADSSHLoginForLinux VM extension installs aadsshlogin PAM module + configures sshd
  • Azure RBAC roles: "Virtual Machine Administrator Login" / "Virtual Machine User Login"
  • Conditional Access policies apply (MFA, device compliance, etc.)
  • No SSH key distribution — certificates are dynamic

Challenge for ACI: The AADSSHLoginForLinux extension is VM/Arc-specific, not available for ACI containers directly. However, we can potentially:

  1. Install aadsshlogin package manually from packages.microsoft.com in our Dockerfile
  2. Or extract Entra ID's SSH CA public key → configure sshd TrustedUserCAKeys manually
  3. Add AuthorizedPrincipalsCommand to call a script checking container ownership

Advantages over custom CA:

  • ✅ No CA key management (Microsoft manages the CA)
  • ✅ Conditional Access policies (MFA, compliant device requirements)
  • ✅ Centralized revocation (disable user in Entra ID → instant SSH revocation)
  • ✅ Azure RBAC integration (familiar Azure permission model)
  • ✅ Audit logs in Entra ID (sign-in logs capture SSH connections)
  • az ssh command — users already have Azure CLI
  • ✅ Works with VS Code Remote SSH via az ssh config

Investigation needed:

  • Can aadsshlogin package be installed in a container without the VM extension?
  • Does Entra ID's SSH CA public key work with ACI's IMDS endpoint (169.254.169.254)?
  • Can we use az ssh with --ip flag to connect directly to ACI container IP on port 22?
  • If ACI doesn't support IMDS for SSH, can we use the client-side certificate with manual CA trust?

Option B: Custom AgentBox CA in Key Vault (fallback)

If Entra ID SSH doesn't work with ACI containers, we fall back to our own CA:

agentbox ssh my-project
  │
  ├── 1. CLI reads user's SSH public key (~/.ssh/id_ed25519.pub)
  │
  ├── 2. CLI authenticates to AgentBox API (Entra ID bearer token)
  │      → POST /api/instances/my-project/ssh-certificate
  │      → Body: { "public_key": "ssh-ed25519 AAAA..." }
  │
  ├── 3. API checks authorization:
  │      → Is this user the container owner, shared, or admin?
  │      → If not → 403 Forbidden
  │
  ├── 4. API signs the public key with the AgentBox CA private key:
  │      → Principals: user@networg.com, agentbox
  │      → Validity: 24 hours
  │      → Extensions: permit-pty, permit-port-forwarding
  │      → Returns: SSH certificate (ssh-ed25519-cert-v01@openssh.com ...)
  │
  ├── 5. CLI saves certificate to ~/.ssh/agentbox-cert.pub
  │
  └── 6. CLI runs:
         ssh -i ~/.ssh/id_ed25519 \
             -o CertificateFile=~/.ssh/agentbox-cert.pub \
             agentbox@my-project.westeurope.azurecontainer.io -p 22

Container sshd configuration (provisioned at image build):

# /etc/ssh/sshd_config additions
TrustedUserCAKeys /etc/ssh/agentbox_ca.pub    # Only accept certificates signed by our CA
PasswordAuthentication no                      # No password login
PubkeyAuthentication yes                       # Certificate auth (subset of pubkey)
AuthorizedKeysFile none                        # No authorized_keys files
PermitRootLogin no                             # No root SSH
AllowUsers agentbox                            # Only the agentbox user

CA key management:

  • CA key pair stored in Azure Key Vault (HSM-backed)
  • API uses Key Vault Sign operation — CA private key never leaves Key Vault
  • CA public key baked into container image at build time
  • Key rotation: new CA key → rebuild image → old certs expire naturally (24hr max)

SSH Security Properties (both options)

  • ✅ No persistent SSH keys on containers (no authorized_keys)
  • ✅ Certificates are short-lived (hours, not permanent)
  • ✅ Identity tied to Entra ID (either natively or via API check)
  • ✅ Each certificate is bound to a specific user principal
  • ✅ Port 22 exposed but only CA-signed certificates accepted
  • ✅ Standard SSH protocol — VS Code Remote SSH, SCP, port forwarding all work natively
  • ✅ Industry standard — SSH certificates used at scale by Google, Facebook, Netflix
  • ✅ No secrets stored in containers — only the CA public key (safe to expose)

VS Code Remote SSH integration (works with either option):

# ~/.ssh/config (managed by agentbox CLI or az ssh config)
Host *.agentbox.networg.com
    User agentbox
    IdentityFile ~/.ssh/id_ed25519
    CertificateFile ~/.ssh/agentbox-cert.pub
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

Users can connect from VS Code: Remote-SSH: Connect to Host...my-project.agentbox.networg.com

Local Docker: SSH works the same way but connects to localhost:<mapped-port>. The agentbox ssh CLI auto-detects local vs cloud containers.

Decision: Investigate Option A first, fall back to Option B

Option A (Entra ID SSH) is superior if it works with ACI because it eliminates custom CA management and adds Conditional Access + centralized revocation. The investigation is a Phase 2.35 task — if it doesn't work with ACI, Option B (Key Vault CA) is proven and straightforward.

Current (Phase 1) — Temporary

  • No authentication — security via obscure DNS names on ACI
  • GH_TOKEN passed manually as secure environment variable
  • Containers run as unprivileged agentbox user (sudo available)
  • No network restrictions within container

Runtime Abstraction

The agentbox CLI already abstracts local vs Azure. Future runtimes plug in:

agentbox spawn my-project                    # local Docker (default)
agentbox spawn my-project --azure            # Azure Container Instances
agentbox spawn my-project --apple            # Apple Containers (future)
agentbox spawn my-project --container-apps   # Azure Container Apps (future)

Each runtime implements:

  • spawn(name, image, env, ports) → url
  • list() → [{name, status, url}]
  • kill(name) → ok
  • logs(name) → stream
  • status(name) → {state, uptime, resources}

Apple Containers (Local ARM64)

Apple Containers (macOS Tahoe / macOS 26) provide native ARM64 container runtime on Apple Silicon. This is a local-only runtime alternative to Docker Desktop:

  • Native performance: No QEMU emulation, native ARM64 execution
  • OCI-compatible: Same container images work (our multi-arch image already supports arm64)
  • Developer use case: Consultants running AgentBox on their Macs get native speed
  • Not for cloud: Apple Containers run only on macOS, not in Azure

Integration approach:

agentbox spawn my-project --apple    # Uses Apple Containers runtime
agentbox spawn my-project            # Uses Docker (current default)
agentbox spawn my-project --azure    # Uses ACI (cloud)

The agentbox CLI runtime abstraction will detect available runtimes and fall back gracefully:

  1. Apple Containers (if macOS Tahoe + available)
  2. Docker Desktop (if Docker daemon running)
  3. ACI (if --azure flag or no local runtime available)