Skip to content

Feature: import and export controls via JSON files #128

Description

@lan17

Summary

  • Add support for exporting controls to a JSON file and importing controls from a JSON file.
  • Export should support either all controls or a selected subset.
  • Import should load controls from JSON, validate them, and persist them in the database.

Motivation

  • Makes it easy to share reusable control bundles across users and environments.
  • Enables publishing JSON files for common policies / recommended control sets.
  • Provides a straightforward backup and migration path for control definitions.

Current behavior

  • Controls can be created, copied, edited, and stored in the database, but there is no import/export flow for sharing or reusing them as files.
  • The UI has a Control Store and per-control editing/copy flows, and the server has control CRUD plus validation endpoints, but nothing supports bulk JSON import/export.

Expected behavior

  • Users can click Export to download either all controls or a selected subset as a JSON file.
  • Users can click Import to upload a JSON file, validate its contents, and store the imported controls in the database.
  • The exported format is shareable so common policies/control bundles can be published and reused.

Reproduction (if bug)

  1. Open the control management / Control Store UI.
  2. Try to share, back up, or move a set of controls between environments.
  3. There is no import/export workflow today.

Proposed solution (optional)

  • Add an export action in the Control Store that serializes either all controls or a user-selected subset to a versioned JSON payload.
  • Add an import flow that parses uploaded JSON, validates each control using existing control-definition and evaluator validation, and persists valid controls.
  • Surface conflicts and invalid entries during import instead of failing silently.
  • Decide import conflict behavior explicitly (for example: skip existing names, overwrite, or import with rename).
  • Decide whether v1 should cover only control definitions or also policy associations.

Additional context

  • This feature lines up with the existing UI Control Store and control CRUD / validation APIs.
  • Open questions to resolve during implementation:
    • JSON schema/versioning for forward compatibility
    • Whether import/export should include policy membership or remain controls-only in v1
    • UX for duplicate names and partial import failures

Activity

  1. added theissue type on Mar 14, 2026
  2. cschanhniem commented on Jun 18, 2026

    @cschanhniem

    Import/export via JSON is the right primitives for shareable control bundles. A few considerations from the policy-as-data perspective:

    Schema versioning: The JSON schema should include a version field and a schema URI. Without it, forward compatibility breaks silently when the control model adds fields. Something like:

    {
      "version": "1.0.0",
      "schema": "https://agentcontrol.dev/schemas/control-bundle/v1.json",
      "controls": [...]
    }

    Policy as file, not just export: The import/export flow is great for sharing. But there is also a case for repo-local policy files (e.g. .agentowners.yml or .agent-control.yml) that live alongside code and are version-controlled. This enables CI/CD enforcement where policy changes are reviewed through the same PR workflow as code changes. The import flow could accept both UI exports and repo-local policy files.

    Control-only vs policy membership: For v1, controls-only is the right scope. But the JSON format should leave room for policy membership later (e.g. which agents or namespaces a control applies to). A scope or target field in the bundle schema would make that extension clean.

    Conflict resolution: For import conflicts, I would bias toward explicit over implicit — surface all conflicts to the user with suggested resolutions, never silently skip or overwrite. The policy author needs to know which controls changed and why. This is especially important when controls encode security invariants — silently dropping a deny control during import is a security regression.

  3. rhiannalitchfield commented on Jul 19, 2026

    @rhiannalitchfield

    Hi! I'd like to pick this up. Before writing code, here is a concrete v1 proposal covering the open questions in the description. Feedback welcome, especially on the conflict handling default.

    Scope for v1: server-side API only, controls only

    • Two endpoints, no UI changes yet. The Control Store buttons can be a follow-up PR once the API shape settles.
    • Controls only, no policy membership, agent attachments, or bindings. Those reference server-local entities (agent names, policy ids) that generally do not exist in the target environment, so including them invites confusing partial failures. The envelope leaves room to add them later.

    Export

    POST /api/v1/controls/export with an optional list of control ids (absent means all controls visible in the caller's namespace). Returns a versioned envelope:

    {
      "format_version": 1,
      "exported_at": "2026-07-19T00:00:00Z",
      "controls": [
        {
          "name": "Block leaked secrets",
          "enabled": true,
          "condition": { ... },
          "action": { "decision": "deny" }
        }
      ]
    }

    Only portable definition fields are exported: name, description, enabled, condition, action, and for template-backed controls the template plus template_values (so they stay editable as templates after import, rather than being flattened). Server-assigned identity is excluded: ids, timestamps, namespace, version history, clone lineage, bindings.

    One thing to decide: evaluator configs can contain sensitive values (API keys for cloud evaluators). I'd propose v1 exports configs as-is and the docs call out that exports may contain secrets, with key redaction as a possible follow-up. Alternative is to redact known secret fields on export, but that makes round-tripping lossy.

    Import

    POST /api/v1/controls/import accepting the same envelope, plus two options:

    • on_conflict: skip (default), overwrite, or rename (imports as "Name (imported)", numbered on repeat). Names conflict per namespace, matching current create behavior. overwrite goes through the existing update path so it creates a new control version rather than destroying history.
    • dry_run: validate everything and return the report without persisting.

    Each control is validated through the existing definition validation path (same code the create and validate endpoints use), so import cannot smuggle in anything the UI would reject. Import is per-item, not all-or-nothing: valid controls import, invalid ones are reported. The response makes every outcome explicit, nothing fails silently:

    {
      "imported": ["Block leaked secrets"],
      "skipped": [{"name": "PII filter", "reason": "name_conflict"}],
      "errors": [{"name": "Bad control", "reason": "unknown evaluator 'foo.bar'"}]
    }

    If all-or-nothing semantics are preferred instead, that is an easy switch, but per-item with a report seems friendlier for the "import a published control bundle" use case where one stale evaluator reference should not block the other nine controls.

    Format versioning

    format_version is an integer, starting at 1. Import rejects unknown versions with a clear error. Future format changes bump the version and import keeps reading older versions.

    Testing

    Round-trip tests (export then import into a clean namespace yields equivalent controls, including template-backed ones), conflict behavior for all three modes, dry run, per-item validation failures, unknown format_version, and namespace isolation.

    If this direction looks right, I'll start with the export endpoint and envelope models, then import in the same PR.

  4. Keesan12 commented on Jul 21, 2026

    @Keesan12

    Import and export sounds like a convenience feature until controls become part of how a team proves what was actually allowed. Then versioning, reviewability, and a clear diff of policy changes become the real product.

    That is a lesson we are learning with MartinLoop. The trust layer around coding agents has to be inspectable before a run and accountable afterward. Teams need to see what rules were in force, what the agent did inside them, and whether the work passed a real finish line.

    Would love to hear how you want teams to review a controls change before it lands. We are building MartinLoop in the open: https://github.com/Keesan12/martin-loop. support@martinloop.com is open for direct feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions