Skip to content

Examples#

A full worked agent.hcl#

required_providers {
  anthropic = {
    source  = "github.com/agentco/provider-anthropic"
    version = "~> 1.2.3"
  }
  filesystem = {
    source  = "github.com/agentco/provider-filesystem"
    version = "~> 1.0"
  }
  ripgrep = {
    source  = "github.com/agentco/provider-ripgrep"
    version = "~> 0.4"
  }
}

provider "anthropic" {
  api_key = env("ANTHROPIC_API_KEY")
}

provider "filesystem" {
  roots = ["."]
}

provider "ripgrep" {}

policy "auto_approve_reads" {
  match  = { kind = "data_source" }
  action = "allow"
}

policy "gate_filesystem_writes" {
  match  = { provider = "filesystem", kind = "resource" }
  action = "ask"
}

policy "block_high_risk" {
  match  = { risk = "critical" }
  action = "deny"
}

agent_profile "default" {
  model {
    primary {
      provider = "anthropic"
      id       = "claude-opus-4-8"
    }
    fallback {
      provider = "anthropic"
      id       = "claude-sonnet-5"
    }
  }

  tools = [
    "filesystem.*",
    "ripgrep.*",
  ]

  max_turns        = 200
  max_cost_usd     = 5.00
  max_wall_clock_s = 3600
}

agent_profile "code-reviewer" {
  model {
    primary {
      provider = "anthropic"
      id       = "claude-sonnet-5"
    }
    fallback {
      provider = "anthropic"
      id       = "claude-haiku-4-5"
    }
  }

  tools = [
    "filesystem.read_file",
    "ripgrep.*",
  ]

  slash_commands = ["compact"]

  max_depth                = 1
  max_concurrent_subagents = 4
  max_turns                = 40
}

settings {
  default_frontend = "tui"
  log_level        = "info"
  telemetry        = true

  retry {
    base_delay_ms  = 500
    backoff_factor = 2
    max_retries    = 5
  }

  observability {
    endpoint           = "localhost:4317"
    protocol           = "grpc"
    sampling_ratio     = 1.0
    traces_enabled     = true
    metrics_enabled    = true
    logs_enabled       = true
    export_interval_ms = 10000
    service_name       = "pluggableharness-agent"
    resource_attrs     = { env = "dev" }
  }
}

This exercises every top-level block type in one file — see blocks-reference.md for required_providers, provider, and settings; policy-dsl.md for policy; and agent-profiles.md for agent_profile. Note the model{} sub-blocks are written multi-line — primary { provider = "x", id = "y" } would be a parse error; see blocks-reference.md#hcl-single-line-blocks-take-only-one-argument.

A worked policy-conflict example#

policy "gate_writes" {
  match  = { provider = "filesystem", kind = "resource" }
  action = "ask"
}

policy "gate_network_writes" {
  match  = { provider = "http-client", kind = "resource" }
  action = "deny"
}

Both rules specify exactly two match fields (provider, kind), so both have the identical specificity tuple (false, true, false, true) (provider and kind set; tool_name and risk not). Under the conflict rule (see policy-dsl.md#conflict-detection), that alone is not enough to flag a conflict — the check also compares every field both rules specify. Here, kind agrees (resource on both) but provider disagrees ("filesystem" vs. "http-client") — a field both rules specify, with different values — so the rules do not conflict: a real call has exactly one provider, so these two rules can never both match the same call. This configuration is accepted.

A non-conflicting, same-tuple pair#

The case the corrected rule exists specifically to allow through:

policy "allow_read_file" {
  match  = { tool_name = "read_file" }
  action = "allow"
}

policy "deny_write_file" {
  match  = { tool_name = "write_file" }
  action = "deny"
}

Both specify only tool_name, so both share specificity tuple (true, false, false, false). Specificity-tuple equality alone does not make these two rules conflict: the one field both rules specify, tool_name, holds different values ("read_file" vs. "write_file") — a real call can only ever have one tool_name, so these two rules can never both match the same call, and are not in conflict. Contrast this with an actual conflict:

policy "a" {
  match  = { tool_name = "read_file" }
  action = "allow"
}

policy "b" {
  match  = { tool_name = "read_file" }
  action = "deny"
}

Same tuple, and the one shared field (tool_name) holds the same value ("read_file") with disagreeing actions — this configuration MUST be rejected as a conflict, naming both "a" and "b".

A worked lock file#

lock_file_version = 1

provider "anthropic" {
  source      = "github.com/agentco/provider-anthropic"
  version     = "1.2.4"
  resolved_at = "2026-07-22T18:04:00Z"
  checksums = {
    "linux_amd64"  = "sha256:1a2b3c4d5e6f7089abcdef0123456789abcdef0123456789abcdef012345678"
    "linux_arm64"  = "sha256:4d5e6f7089abcdef0123456789abcdef0123456789abcdef0123456789abcd12"
    "darwin_amd64" = "sha256:7a8b9c0123456789abcdef0123456789abcdef0123456789abcdef012345678"
    "darwin_arm64" = "sha256:0d1e2f3456789abcdef0123456789abcdef0123456789abcdef01234567890a"
  }
}

provider "filesystem" {
  source      = "github.com/agentco/provider-filesystem"
  version     = "1.0.2"
  resolved_at = "2026-07-20T09:12:00Z"
  checksums = {
    "linux_amd64"  = "sha256:9f8e7d6c5b4a3928170695a4b3c2d1e0f9e8d7c6b5a4938271605f4e3d2c1b0"
    "linux_arm64"  = "sha256:2c1b0a9f8e7d6c5b4a3928170695a4b3c2d1e0f9e8d7c6b5a49382716059f8e"
    "darwin_amd64" = "sha256:5b4a3928170695a4b3c2d1e0f9e8d7c6b5a4938271605f4e3d2c1b09f8e7d6c"
    "darwin_arm64" = "sha256:8271605f4e3d2c1b09f8e7d6c5b4a3928170695a4b3c2d1e0f9e8d7c6b5a493"
  }
}

See lock-file.md for the version pre-check and checksum verification this file's loading and use involves.