{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Sandseal Settings",
  "description": "Configuration for the Sandseal AI agent sandbox",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "JSON Schema reference for IDE autocompletion"
    },
    "description": {
      "type": "string",
      "description": "Human-readable label for this settings file, shown by `sandseal config list`"
    },
    "$replace": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Dot-separated keys this layer replaces wholesale instead of merging into the layers below (e.g. \"environment\", \"files.include\"). Without it a layer can only add or overwrite keys, never remove an inherited one."
    },
    "files": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "exclude": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "include": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "minLength": 1
          },
          "propertyNames": {
            "minLength": 1
          }
        }
      }
    },
    "dependencies": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      }
    },
    "container": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "memoryLimit": {
          "type": "string",
          "minLength": 1
        },
        "memorySwapLimit": {
          "type": "string",
          "minLength": 1
        },
        "baseImage": {
          "type": "string",
          "minLength": 1
        },
        "aptMirror": {
          "type": "string",
          "minLength": 1,
          "description": "Ubuntu archive to install packages from, e.g. http://cz.archive.ubuntu.com/ubuntu. Defaults to whatever the base image ships.",
          "pattern": "^https?://"
        }
      }
    },
    "hooks": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "setup": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "script": {
              "type": "string",
              "minLength": 1,
              "description": "Path to a script FILE on the host, run at image build time. Relative paths resolve against the project directory. This is not a shell command \u2014 an inline command is treated as a filename and the hook is skipped."
            }
          },
          "description": "Runs at image build time. Changing it requires a rebuild."
        },
        "prestart": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "script": {
                "type": "string",
                "minLength": 1,
                "description": "Path to a script FILE on the host, copied into the container and run before the agent starts. Relative paths resolve against the project directory. Not a shell command."
              }
            },
            "required": [
              "script"
            ]
          },
          "description": "Scripts run inside the container before the agent starts, in order."
        },
        "setupHost": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "script": {
                "type": "string",
                "minLength": 1,
                "description": "Path to a script FILE on the host, run on the host before the container starts. Not a shell command."
              }
            },
            "required": [
              "script"
            ]
          }
        },
        "cleanupHost": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "script": {
                "type": "string",
                "minLength": 1,
                "description": "Path to a script FILE on the host, run on the host after the container stops. Not a shell command."
              }
            },
            "required": [
              "script"
            ]
          }
        }
      }
    },
    "workspace": {
      "type": "object",
      "additionalProperties": false,
      "description": "Directory mounted into the sandbox, giving the agent visibility into other projects",
      "properties": {
        "dir": {
          "type": "string",
          "minLength": 1,
          "description": "Host directory path to mount"
        },
        "readwrite": {
          "type": "boolean",
          "description": "Mount read-write instead of read-only (default: false)"
        }
      },
      "required": [
        "dir"
      ]
    },
    "environment": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      },
      "propertyNames": {
        "minLength": 1
      }
    },
    "network": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "mode": {
          "type": "string",
          "enum": [
            "bridge",
            "host"
          ],
          "default": "bridge",
          "description": "Network mode for the sandbox container. 'bridge' (default) isolates the container network. 'host' shares the host network namespace \u2014 use only when necessary."
        },
        "services": {
          "type": "object",
          "additionalProperties": {
            "type": "string",
            "minLength": 1
          },
          "propertyNames": {
            "minLength": 1
          },
          "description": "Service endpoints accessible from the sandbox. Maps hostname to target (e.g. \"db.dev\": \"host-gateway:5432\"). Generates extra_hosts entries in docker-compose."
        }
      }
    },
    "docker": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "passthrough": {
          "type": "boolean",
          "default": false,
          "description": "Mount Docker socket into the sandbox, giving the agent full Docker access. Disabled by default for security."
        }
      }
    },
    "memory": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "scope": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "project": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,100}$",
              "description": "Name this sandbox's notes are filed under. Defaults to the project directory's name, which is wrong when one sandbox spans several projects, or when the same project is opened from different directories. Letters, digits, dot, underscore and hyphen only."
            },
            "crossProject": {
              "type": "boolean",
              "default": true,
              "description": "Whether recall searches the whole memory space or only this project. Default true: notes written from other projects stay reachable. Writes are pinned to this project either way."
            }
          },
          "description": "Which slice of memory this sandbox writes to and reads from."
        }
      },
      "description": "Memory configuration for this sandbox."
    },
    "gc": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "onStart": {
          "type": "boolean",
          "default": true,
          "description": "Whether `sandseal start` first cleans up sandboxes left behind by a CLI that was killed, and containers from sessions that already ended. Default true. A sandbox someone is still using is never touched \u2014 a running CLI holds a lock on its instance record, and locked records are skipped. Turn this off only if you deliberately keep sandboxes running with no CLI attached; `sandseal gc` still works either way."
        }
      },
      "description": "When the collector for abandoned sandboxes runs on its own."
    },
    "terminal": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "title": {
          "type": "boolean",
          "default": true,
          "description": "Whether `sandseal start` names the host terminal window after the sandbox, as `<project>\u00b7<instance> [profile]`. The name is put in front of whatever title the agent sets, so the agent's own topic stays visible; with several sandboxes open it is what tells the windows apart. Under tmux or screen the same title is also sent as DCS passthrough, which the multiplexer discards silently if it does not allow it. Turn this off to leave the window title entirely to the agent."
        }
      },
      "description": "How the sandbox presents itself in the terminal it was started from."
    },
    "clipboard": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "default": true,
          "description": "Whether the sandbox can read the host clipboard, read-only. This is what makes pasting an image into the agent (Ctrl+V) work: the CLI serves the clipboard over a socket in the instance tmp dir, and an `xclip` stand-in inside the container forwards the agent's requests to it. Only the requested bytes cross into the sandbox; the display, Windows drive and clipboard history do not. Turn it off where the clipboard may hold secrets the sandbox must not see \u2014 a sandboxed agent could read it at any time, not only when you paste."
        }
      },
      "description": "What the sandbox may do with the host clipboard."
    }
  }
}
