Add bbox agents import/export manifest files (#203)#215
Conversation
Implements #203 (BYO agents Phase 4): lets a reusable agent definition live as a standalone, shareable YAML manifest instead of only inline under agents: in the global config. - bbox agents import <manifest>: reads a standalone manifest (top-level name + the same fields as an agents:<name> block), validates it with the existing ValidateCustomAgent, and appends it to the global config. Reuses the agents add path: same UpsertAgent write (comment-preserving), --force/--config/--json flags, and JSON receipt (Command: agents
agents add checked --egress-profile and --mcp-authz-profile flag values before calling ValidateCustomAgent, but agents import (and the plain config loader) skipped those checks entirely, so a typo'd profile name could be written to config and pass doctor. Move both checks into the shared validator so add, import, doctor, and the loader are all strict uniformly.
Cover the two gaps a review found: credentials.persist and settings entries weren't exercised through export/import, and exporting against a missing config file had no test locking in the "not declared" error path.
Follows the same-commit precedent set by the two prior PRs that added agent subcommands.
|
Ran a multi-agent review of this PR (architecture/DDD layering, security, Go code quality/tests, and docs sync) and pushed three follow-up commits addressing what came out of it:
Everything else in the review came back clean — domain purity, secret handling on export (only |
Closes #203. Follow-up to #191 (epic); builds on the declarative custom-agent model from #200 (Phase 1) and the
bbox agents add/initCLI from #210 (Phase 2).What's new
bbox agents import <manifest>— reads a standalone agent manifest (a YAML file with a top-levelnameplus the same fields as anagents:<name>block), validates it with the existingValidateCustomAgent, and appends it to the global config (~/.config/broodbox/config.yaml, or--config). Refuses to overwrite a built-in or an existing custom agent unless--force. Flags:--config,--force/-f,--json.bbox agents export <name>— emits a standalone manifest to stdout for an existing custom agent. Refuses built-ins and unknown agents.Manifest format
Same fields as an
agents:<name>entry, with a top-levelname:Design notes
importreusesValidateCustomAgent+UpsertAgent+ thebuildAddReceipt/emitAddResultreceipt infra fromagents add— same validation, same comment-preserving write, same--force/--config/--jsonflags, same JSON receipt (withCommand: "agents import"to distinguish it). The new code is CLI (cmd/bbox) + manifest file I/O (internal/infra/config) + a thin domain mapping (pkg/domain/config).AgentManifestdomain type (pkg/domain/config/manifest.go, pure) inlinesAgentOverride+ adds a top-levelName, so manifest fields exactly track theagents:<name>block with no second field list to drift.LoadManifest/MarshalManifest(internal/infra/config/manifest.go) reuseconfigfile.ReadFile(size cap, regular-file check) andDecodeStrict(strict unknown-field checking) so a malformed or oversized manifest fails loudly rather than being silently dropped.exportstripsdefault_env(host-supplied values) before marshalling; only env names/patterns (env_forward,env_required) are emitted — consistent withagents inspect/doctor.exportthenimportof the same agent reproduces equivalent fields (verified by test).importwrites only to the global config, never a workspace.broodbox.yaml. Import paths are operator-supplied (like--config), so symlinks are accepted, matching the global config loader.cmd/bbox.Acceptance criteria — verified end to end
```
bbox agents import ./broodbox-agent.yaml && bbox agents doctor # PASS
bbox agents export # produces a manifest that re-imports cleanly
```
Tests
task fmtclean,task lint0 issues,task test(race) all 34 packages pass. New tests cover: import end-to-end (import → doctor PASS), export → re-import round-trip,--forceoverwrite, built-in refusal, missing-name rejection, invalid-manifest rejection, strict unknown-field rejection,default_envvalue stripping on export, built-in/unknown export refusal, and the domain manifest struct's inline-field YAML round-trip.