Skip to content

Add bring-your-own AI agent support #191

Description

@JAORMX

Summary

Add first-class support for bring your own AI agent/client/harness in Brood Box, so users can run and register arbitrary AI coding tools without forking this repo, adding a pkg/clients/<name> package, or maintaining an upstream Dockerfile here.

The north-star UX should be:

bbox agents import ghcr.io/acme/aider-bbox:latest
bbox agents doctor aider
bbox aider

And for simple/power-user cases:

agents:
  my-harness:
    image: ghcr.io/me/my-harness:latest
    command: ["my-harness"]
    env_forward: ["MY_HARNESS_*", "OPENAI_API_KEY"]
    mcp:
      mode: env

No Go code, no Brood Box fork, no custom infra package, and no built-in Dockerfile should be required for the common path.

Background / current state

Brood Box already has the right internal seam:

  • pkg/domain/agent.Agent is the declarative runtime spec: image, command, env forwarding, defaults, egress hosts, credential paths, settings manifest.
  • pkg/domain/agent.ClientEntry pairs an Agent with an optional Plugin.
  • pkg/domain/agent.Plugin currently supports behavior that cannot be expressed declaratively, especially:
    • MCPConfig() agent.MCPInjector
    • Seeder() credential.Seeder
  • Built-ins live under pkg/clients/<name> and expose New() agent.ClientEntry.
  • The in-memory registry supports both:
    • data-only agents via Registry.Add(agent.Agent)
    • full client entries via Registry.AddEntry(agent.ClientEntry).
  • Global config currently supports unknown/custom agents through agents.<name> but only for a limited data-only subset: image, command, env, CPUs, memory, etc.

The pain point: each first-class harness currently needs custom code and often custom image wiring in this repo:

  • pkg/clients/claudecode
  • pkg/clients/codex
  • pkg/clients/gemini
  • pkg/clients/hermes
  • pkg/clients/opencode
  • images/<name>/Dockerfile
  • Taskfile image targets

That does not scale to arbitrary agents like Aider, Goose, Cursor Agent CLI, custom internal harnesses, research agents, etc.

Design principle

Treat this as two layers:

  1. Declarative BYO agents for the common CLI path.
  2. Trusted behavioral clients for built-ins and SDK consumers.

Do not start with arbitrary host-side plugin loading as the normal CLI extension mechanism. Dynamic plugins introduce trust, signing, ABI/API stability, supply-chain, and host-execution concerns. The default UX should be data-driven.

Keep the current distinction internally:

  • Agent = declarative VM runtime spec.
  • ClientEntry = Agent + optional trusted behavior.
  • Plugin = implementation detail for built-ins / SDK consumers.

User-facing docs can continue to say agent.

Proposed UX tiers

1. Run any image once

Good for experimentation without editing config:

bbox run-image ghcr.io/me/aider-bbox:latest -- aider --model gpt-5

Useful flags:

bbox run-image ghcr.io/me/aider-bbox:latest \
  --name aider \
  --env OPENAI_API_KEY \
  --env 'AIDER_*' \
  --memory 4g \
  --cpus 4 \
  --mcp \
  --mcp-authz-profile safe-tools \
  --egress-profile standard \
  --allow-host api.openai.com:443 \
  -- aider

This mode should be explicitly ephemeral:

  • no credential persistence by default
  • no settings import by default
  • no custom config-file injection unless requested
  • no env forwarding unless requested

2. Register durable custom agents in config

Minimal valid custom agent:

agents:
  aider:
    image: ghcr.io/acme/aider-bbox:latest
    command: ["aider"]

Richer example:

agents:
  aider:
    description: "Aider coding assistant"
    image: ghcr.io/acme/aider-bbox:latest
    command: ["aider"]

    env_forward:
      - OPENAI_API_KEY
      - ANTHROPIC_API_KEY
      - OPENROUTER_API_KEY
      - AIDER_*

    env_required:
      - OPENAI_API_KEY

    default_env:
      AIDER_YES_ALWAYS: "false"

    cpus: 4
    memory: 4g
    tmp_size: 2g

    egress_profile: standard
    egress_hosts:
      locked:
        - name: api.openai.com
          ports: [443]
      standard:
        - name: api.openai.com
          ports: [443]
        - name: github.com
          ports: [443, 22]

    credentials:
      persist:
        - .aider/
        - .config/aider/

    settings:
      entries:
        - category: settings
          host_path: .aider.conf.yml
          guest_path: .aider.conf.yml
          kind: file
          optional: true

    mcp:
      enabled: true
      mode: env

Then users run it like a built-in:

bbox aider

3. Import/export reusable agent manifests

Support an agent manifest as a standalone file and, later, embedded in an OCI image.

Example manifest:

# broodbox-agent.yaml
name: aider
image: ghcr.io/acme/aider-bbox:latest
command: ["aider"]
env_forward:
  - OPENAI_API_KEY
  - AIDER_*
env_required:
  - OPENAI_API_KEY
mcp:
  mode: env
egress_profile: standard

Commands:

bbox agents import ./broodbox-agent.yaml
bbox agents import ghcr.io/acme/aider-bbox:latest
bbox agents export aider > aider.broodbox-agent.yaml
bbox agents inspect aider
bbox agents doctor aider

Optional image convention:

LABEL org.stacklok.broodbox.agent=/usr/share/broodbox/agent.yaml
COPY broodbox-agent.yaml /usr/share/broodbox/agent.yaml

Proposed CLI surface

Agent management

bbox agents list
bbox agents add NAME --image IMAGE --command COMMAND [--env ENV_PATTERN...]
bbox agents init
bbox agents inspect NAME
bbox agents doctor NAME
bbox agents import SOURCE
bbox agents export NAME

Keep bbox list as an alias for bbox agents list.

Inspect output should show

  • image
  • command
  • env vars that will be forwarded, names only, never values
  • missing required env vars
  • resource defaults
  • egress profile and allowed hosts
  • MCP mode and authz profile
  • credential paths
  • settings mappings
  • source of each effective field: built-in, global config, workspace config, CLI

Doctor checks should include

  • config schema validity
  • agent name validity
  • OCI image reference parse / pullability or cache presence
  • command configured; optionally command exists in image if cheap to inspect
  • required env vars present
  • env patterns valid; reject empty, bare *, leading-star patterns
  • egress hostnames valid; reject IP literals where policy disallows them
  • MCP injection config valid
  • credential/settings paths safe and relative to guest home
  • workspace-local config did not attempt unsafe overrides

Universal runtime contract

For all agents, especially BYO agents, Brood Box should inject stable BBOX_* env vars:

BBOX_AGENT_NAME=aider
BBOX_WORKSPACE=/workspace
BBOX_HOME=/home/sandbox
BBOX_MCP_URL=http://192.168.127.1:4483/mcp
BBOX_MCP_AUTHZ_PROFILE=safe-tools
BBOX_SESSION_ID=...
BBOX_GIT_TOKEN_AVAILABLE=true|false
BBOX_SSH_AGENT_AVAILABLE=true|false

This gives custom harness authors a stable integration point and avoids config-file mutation for tools that can read env vars.

Generic MCP support

Current built-ins often need custom MCPInjector code because every agent has a different config file format/layout.

For BYO, prefer two generic modes:

mcp.mode: env

Only expose BBOX_MCP_URL and related env vars. The harness reads them.

mcp:
  enabled: true
  mode: env

mcp.mode: config

Declaratively patch a JSON/JSONC/TOML/YAML config file in the guest home:

mcp:
  enabled: true
  mode: config
  inject:
    - guest_path: .config/aider/mcp.json
      format: json
      merge:
        mcpServers:
          broodbox:
            type: streamable-http
            url: "${BBOX_MCP_URL}"

The generic implementation can live in infra; the config shape should remain pure domain data. Built-ins with special formats can keep custom Plugin.MCPConfig().

Declarative settings import

Expose the existing settings manifest concept to config-defined agents. Example:

settings:
  entries:
    - category: rules
      host_path: .aider/rules
      guest_path: .aider/rules
      kind: directory
      optional: true
    - category: settings
      host_path: .aider/config.json
      guest_path: .aider/config.json
      kind: merge-file
      format: json
      allow_keys:
        - model
        - editor
        - theme

This generalizes what built-ins currently hardcode for Claude/Codex/OpenCode/Gemini/Hermes.

Declarative credential persistence

Expose credential persistence paths for custom agents, but with strong constraints:

credentials:
  persist:
    - .aider/

Security recommendation:

  • Global config may define credential persistence for custom agents.
  • Workspace-local .broodbox.yaml should not be allowed to introduce new credential paths.
  • Paths must be relative to the sandbox user home and must not escape it.
  • No host-side credential seeding for custom agents by default.

Image authoring UX

Document three image paths.

Option 1: Use Brood Box base image

FROM ghcr.io/stacklok/brood-box/base:latest

RUN pip install aider-chat
USER sandbox

Option 2: Bring any compatible Linux image

Document the minimum contract the image must satisfy. For example:

  • compatible with Brood Box / go-microvm boot flow
  • expected sandbox user/home behavior
  • shell/command available
  • agent binary on PATH or explicit command

Option 3: Self-describing image

Embed /usr/share/broodbox/agent.yaml and allow bbox agents import IMAGE.

Suggested defaults for custom agents

Custom/BYO agents should be safer than built-ins by default:

env_forward: []
egress_profile: standard
mcp:
  enabled: true
  authz:
    profile: safe-tools
settings:
  enabled: false
credentials:
  persist: []

Rationale:

  • no env forwarding by default avoids accidental secret exposure
  • safe-tools is better than full-access for unknown harnesses
  • credential/settings import should be explicit
  • standard egress is a practical middle ground

Proposed implementation phases

Phase 1: Better data-only custom agents

Extend current agents.<name> config for unknown/custom agents to support more declarative fields, likely including:

  • description
  • default_env
  • env_required
  • tmp_size
  • credential_paths or credentials.persist
  • settings / settings manifest entries
  • per-profile egress_hosts
  • generic mcp.mode: env
  • universal BBOX_* env vars

This should build on the existing data-only custom agent path rather than requiring plugins.

Phase 2: Agent management CLI

Add:

bbox agents list
bbox agents add
bbox agents init
bbox agents inspect
bbox agents doctor

Phase 3: Generic MCP config injection

Add mcp.mode: config with declarative JSON/JSONC/TOML/YAML merge targets.

Phase 4: Import/export manifests

Support:

bbox agents import ./broodbox-agent.yaml
bbox agents export aider

Phase 5: OCI image manifests

Support:

bbox agents import ghcr.io/acme/aider-bbox:latest

by reading an embedded manifest path declared by OCI label or a well-known path.

Phase 6: Advanced trusted plugins, if still needed

For advanced behavior that cannot be data-driven:

  • Keep Go SDK support via agent.ClientEntry + Plugin.
  • Consider CLI-side external drivers only later, with explicit trust boundaries.
  • Prefer a signed executable driver protocol over loading arbitrary Go plugins.

Architecture constraints

Preserve strict DDD layering:

  • Pure config/domain types belong in pkg/domain/config, pkg/domain/agent, pkg/domain/settings, etc.
  • Generic config-file mutation/injection implementations belong in internal/infra.
  • Sandbox orchestration belongs in pkg/sandbox and should depend only on domain interfaces/types.
  • Built-in special cases may remain in pkg/clients/<name>.
  • SDK consumers should continue to be able to construct custom agent.ClientEntry values and pass them to a registry.

Acceptance criteria

A first useful iteration should allow this without adding Go code or a Dockerfile to this repo:

agents:
  aider:
    image: ghcr.io/acme/aider-bbox:latest
    command: ["aider"]
    env_forward:
      - OPENAI_API_KEY
      - AIDER_*
    mcp:
      mode: env

Then:

bbox agents doctor aider
bbox aider

The run should:

  • boot the configured image
  • run the configured command
  • forward only explicitly configured env vars
  • expose stable BBOX_* env vars, including MCP URL when MCP is enabled
  • preserve snapshot isolation and review behavior
  • respect egress/MCP tighten-only security semantics
  • require no pkg/clients/<name> package
  • require no images/<name>/Dockerfile in this repo

Notes / open questions

  • Should the richer config remain under agents: for backward compatibility, or should we introduce a separate clients: section and normalize both internally?
  • Should custom agent default egress be standard or permissive for compatibility?
  • Should BYO default MCP authz be safe-tools even though built-ins default to full-access today?
  • How much image inspection should agents doctor do locally vs only validating config?
  • What is the exact minimum image contract for run-image / imported OCI images?
  • Should workspace-local config be able to add custom agents at all, or only tighten existing/global agents? This affects trust semantics for untrusted repos.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions