Skip to content

Add bbox agents add/init scaffolding (#201) - #210

Merged
JAORMX merged 4 commits into
mainfrom
byo-agents-phase2-add-init
Jul 2, 2026
Merged

Add bbox agents add/init scaffolding (#201)#210
JAORMX merged 4 commits into
mainfrom
byo-agents-phase2-add-init

Conversation

@JAORMX

@JAORMX JAORMX commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #191 (epic); builds on Phase 1 (#200). Adds the Phase 2 CLI for authoring a custom (bring-your-own) agent without hand-editing YAML.

What's new

  • bbox agents add NAME --image IMAGE --command CMD [flags] — appends a validated custom agent to the global config (~/.config/broodbox/config.yaml or --config), then runs the existing ValidateCustomAgent and prints the result. Refuses to overwrite a built-in or an existing custom agent unless --force. Flags: --image, --command (repeatable), --description, --env, --env-required, --memory, --cpus, --tmp-size, --egress-profile, --allow-host, --mcp, --mcp-authz-profile, --force, --json.
  • bbox agents init [NAME] — prints a commented starter agents: stanza to stdout, reusing the documentation from writer.go's default template (extracted into a shared customAgentExampleBlock so the two can't drift).
  • JSON receipt on agents add and agents doctor --json — a re-checkable, paste-safe record: agent summary, env vars by name only with present/missing, MCP state, validator version, before/after config SHA-256 fingerprints (add), writer comment-handling note, and explicit non-effects. (Implements @HarperZ9's receipt suggestion.)

Design notes

  • Reuses the pure config.AgentFromOverride and config.ValidateCustomAgent from Add declarative bring-your-own agent support (#191 Phase 1) #200. New code is CLI (cmd/bbox) + a config-file mutation in internal/infra/config; the domain stays pure (only a CustomAgentValidatorVersion constant added).
  • Custom agents remain global-onlyagents add never targets a workspace .broodbox.yaml.
  • Comment preservation: UpsertAgent does a comment-preserving YAML node round-trip (encoding the document node so leading comments survive). A comment-only file — like the config init template — parses to an empty tree, so it is kept verbatim and the new stanza appended; documentation is never dropped. Verified: two sequential adds into a fresh config init keep all 254 comment lines under a single agents: key.

Acceptance criteria — verified end to end

bbox agents add aider --image ghcr.io/acme/aider-bbox:latest --command aider --env OPENAI_API_KEY --mcp
bbox agents doctor aider   # [PASS]

No manual YAML editing.

Tests

task fmt clean, task lint 0 issues, task test (race) all 34 packages pass. New tests cover the flag→override mapping, add/doctor end-to-end + JSON receipt, built-in/duplicate refusal, init output, and the mutation's comment-preservation / append / force / non-mapping-root cases.

🤖 Generated with Claude Code

JAORMX and others added 4 commits July 2, 2026 06:24
Phase 2 of bring-your-own agents: author a custom agent without hand-
editing YAML.

- `agents add NAME --image ... --command ...` appends a validated custom
  agent to the GLOBAL config, refusing to overwrite a built-in or existing
  custom agent unless --force. Reuses the pure AgentFromOverride and
  ValidateCustomAgent from #200.
- `agents init [NAME]` prints a commented starter agents: stanza to stdout,
  reusing the default config template's documentation.
- UpsertAgent (infra) performs a comment-preserving YAML node round-trip;
  a comment-only file (the `config init` template) is kept verbatim and the
  stanza appended, so documentation is never dropped.
- Both `agents add` and `agents doctor --json` emit a machine-readable
  receipt: agent summary, env vars by name only, MCP state, validator
  version, before/after config fingerprints, and explicit non-effects.

Custom agents remain global-only; the domain stays pure (writing is I/O in
internal/infra/config + cmd/bbox).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAvtCCPrzJgL7z7WMPsLTG
CLAUDE.md's custom-agent section still listed only list|inspect|doctor;
add and init were added in a prior PR but never mentioned.
buildAddAgentOverride only applied the authz profile inside the
--mcp branch, so setting --mcp-authz-profile without --mcp silently
had no effect and the receipt showed no mcp section at all.
Two concurrent `bbox agents add` invocations could silently lose one
write (last-writer-wins, no error) since the read-modify-write had no
locking. Serialize it with a blocking advisory flock on a sidecar
file, matching the pattern already used in internal/infra/vm/firmware.go,
and make the write itself atomic via temp-file-plus-rename so a crash
mid-write can't corrupt the config or leave a torn file visible to
readers.

Also detect "---"-separated multi-document YAML in rootMapping and
error instead of silently dropping every document after the first,
which yaml.Unmarshal would otherwise do on re-encode.
@jhrozek

jhrozek commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review summary

Ran a full review of this PR (adapted ToolHive review categories to brood-box's DDD layers) using go-security-reviewer, go-architect, and go-expert-developer in parallel, focused on: path-traversal safety for credential/settings paths, secret handling in the new JSON receipt, config-file write safety, egress/MCP privilege defaults, and general Go/test quality.

Security review: clean, no findings — safeRelPath correctly blocks traversal, the receipt never leaks env values, file perms are correct, egress/MCP defaults are as safe as documented.

Fixed in follow-up commits:

  • --mcp-authz-profile was silently ignored when --mcp wasn't also passed — now rejected with a clear error.
  • internal/infra/config/mutate.go's UpsertAgent did an unlocked read-modify-write, so two concurrent bbox agents add calls could silently lose one write. Added a blocking advisory flock (matching the existing pattern in internal/infra/vm/firmware.go) plus atomic temp-file+rename writes, with a new concurrency regression test.
  • rootMapping used yaml.Unmarshal, which silently drops all but the first document in a ----separated multi-document YAML file on re-encode — now detected and rejected.
  • CLAUDE.md's custom-agent doc still listed only list|inspect|doctor; updated to include add|init.

Deferred, tracked separately:

  • Minor duplication of the "safer defaults" resolution logic (MCP authz / egress profile) across 3-5 call sites in cmd/bbox — pure refactor, no functional bug, scoped but not prioritized for this PR.
  • New bug found during that scoping, split out to Custom agent's mcp.enabled: false override doesn't disable the runtime MCP proxy #211: a custom agent's own mcp.enabled: false override is correctly reflected in inspect/doctor output but doesn't actually disable the runtime MCP proxy wiring in main.go.

All fixes verified via task fmt && task lint && task test (race detector on) after each change.

@jhrozek jhrozek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and fixed the issues found (see comment above). LGTM.

@JAORMX
JAORMX merged commit e9b531a into main Jul 2, 2026
8 checks passed
@JAORMX
JAORMX deleted the byo-agents-phase2-add-init branch July 2, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants