# Inference Gateway Skills Adl

> ADL (Agent Definition Language) Expert

- Skill: `tomevault-io/inference-gateway-skills-adl` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/inference-gateway-skills-adl`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/inference-gateway-skills-adl/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/inference-gateway-skills-adl

---


# ADL (Agent Definition Language) Expert

Use this skill when working in any project whose root contains an `agent.yaml`
manifest with `apiVersion: adl.inference-gateway.com/v1` (or `v2`+ when it
ships) together with an `.adl-ignore` file. That pair is the fingerprint of a
project generated by [`adl-cli`](https://github.com/inference-gateway/adl-cli)
from a schema in [`inference-gateway/adl`](https://github.com/inference-gateway/adl).
Also use it when the user is authoring a fresh manifest (`adl init`) or
planning the domain model for a new agent.

## Mental model

ADL is "OpenAPI for AI agents". A single declarative manifest names everything
the agent needs - capabilities, the AI provider, services, function-call
tools, markdown skills, server/auth, language runtime, sandbox, deployment -
and `adl-cli` turns it into an enterprise-ready, A2A-compatible project. You
own the manifest and the bodies of TODO placeholders; the CLI owns everything
else and will regenerate it on demand.

**The contract has two halves you must keep straight:**

| Concept                                                                          | Where it lives | Who owns it                      | Regenerated on `adl generate`?  |
| -------------------------------------------------------------------------------- | -------------- | -------------------------------- | ------------------------------- |
| Manifest (`agent.yaml`)                                                          | repo root      | you                              | no - source of truth            |
| Scaffolding (`main.go`, `config/`, `internal/<service>/`, `Dockerfile`, CI, ...) | various        | the generator                    | yes                             |
| Custom tool bodies (`tools/<name>.{go,rs,ts}`)                                   | `tools/`       | you, after a TODO scaffold       | no - protected by `.adl-ignore` |
| Custom service impls (`internal/<service>/*.go`)                                 | `internal/`    | you, after a TODO scaffold       | no - protected by `.adl-ignore` |
| Skills (`skills/<id>/SKILL.md`)                                                  | `skills/`      | you (bare) or upstream (sourced) | no - whole dir protected        |

If you forget which half a file belongs to, `cat .adl-ignore` - anything
matched there survives `adl generate --overwrite`.

**Spec at a glance.** Every `spec.*` top-level field the v1 schema defines:

| `spec.*` field | Required? | Purpose                                                                                               |
| -------------- | --------- | ----------------------------------------------------------------------------------------------------- |
| `capabilities` | yes       | A2A feature flags - `streaming`, `pushNotifications`, `stateTransitionHistory` (all three booleans)   |
| `server`       | yes       | `port` (1-65535), optional `scheme`, `debug`, `auth.enabled`                                          |
| `language`     | yes       | At least one of `go`, `typescript`, `rust` (each with its own required pair, e.g. `module`+`version`) |
| `agent`        | no        | LLM provider/model/systemPrompt/maxTokens/temperature                                                 |
| `card`         | no        | Static A2A agent-card metadata served at `.well-known/agent-card.json`                                |
| `services`     | no        | Domain services declared as ports (`type`, `interface`, `factory`, `description`)                     |
| `config`       | no        | Arbitrary per-section config maps; one section per service (env-mapped)                               |
| `tools`        | no        | Function-call entrypoints - reserved built-in ids and user tools                                      |
| `skills`       | no        | Markdown playbooks - registry / GitHub `source:` / `bare: true`                                       |
| `acronyms`     | no        | String list the generator preserves in generated identifier casing                                    |
| `artifacts`    | no        | `enabled: true` to generate an artifacts server (filesystem or MinIO backend)                         |
| `hooks`        | no        | `post: [...]` commands the CLI runs after each `adl generate`                                         |
| `scm`          | no        | `provider`, `url`, `github_app`, `issue_templates`, `dependabot`, `ci`, `cd`                          |
| `development`  | no        | `sandbox.{flox,devcontainer,dockerCompose}` + `ai.{claudecode,codex,gemini,opencode,infer}`           |
| `deployment`   | no        | `type: kubernetes \| cloudrun` plus the matching block                                                |

The sections below cover each in turn. Anything not in this table is not in
v1 - if you see it in an existing manifest, treat it as a CLI extension and
verify against `adl-cli`'s changelog.

## Schema-first / domain-first workflow

ADL is schema-driven by design: the manifest is the domain model. Sequence
work so the manifest leads and the code follows.

1. **Model the domain in YAML before touching any source file.**
   - Name the bounded context in `metadata.name` (lowercase, hyphenated) and
     pin `metadata.version` (semver - `^\d+\.\d+\.\d+$`).
   - **Declare the required spec frame first:** `spec.capabilities` (all
     three booleans - `streaming`, `pushNotifications`,
     `stateTransitionHistory`), `spec.server.port`, and at least one
     `spec.language.{go|typescript|rust}` target. Almost always set
     `spec.agent` (provider + model) and `spec.card` (A2A discovery) too -
     they're optional in the schema but the agent is useless without them.
   - Enumerate **services** (`spec.services.*`) as ports: each gets a
     `type` (`service`/`repository`/`client`/`middleware`), an `interface`,
     a `factory`, and a description. One service per responsibility - don't
     conflate `database` and `cache`.
   - Enumerate **config sections** (`spec.config.*`) as value objects. Use
     dotted-name injection (`config.database`) to give a tool _only_ the
     subsection it needs.
   - Enumerate **tools** (`spec.tools[]`) as commands - the verbs the model
     can call. Each one declares its JSON Schema (`schema:`) and the services
     it depends on (`inject:`).
   - Enumerate **skills** (`spec.skills[]`) as the procedural knowledge - the
     "how" and "when" for using the tools. Skills are markdown, not code.
2. **Validate the manifest before generating.** `adl validate agent.yaml`
   checks shape against the pinned schema and rejects typos in reserved
   namespaces (e.g. `spec.config.tools.bash.tymeout_seconds`).
3. **Generate.** `adl generate --file agent.yaml --output .` writes the
   scaffold. The first run creates `.adl-ignore` with every file containing a
   TODO marked for protection. Re-run with `--overwrite` to refresh
   non-protected scaffolding after a manifest edit.
4. **Implement TODO bodies.** Each generated `tools/<name>.{go,rs,ts}` and
   each `internal/<service>/*.{go,rs,ts}` ships with `// TODO:` markers
   spelling out what to replace. The factory signature and the handler
   signature are locked - don't rename them, or you'll desync from the
   generator's expectations on the next regeneration of `main.go`.
5. **Test what you implemented** (see "Testing custom tools" - this is the
   non-negotiable part).
6. **Run.** `task build && task run` (Go/Rust) or the equivalent in the chosen
   language. The generated `Taskfile.yml` is the entry point.

## Capabilities, the LLM, and the agent card

Three top-of-spec blocks shape what the agent advertises, which model it
talks to, and how clients discover it. Set them before anything domain-
specific.

**`spec.capabilities` (required).** All three booleans must be present -
the validator rejects the manifest if any is missing:

```yaml
capabilities:
  streaming: true # SSE-based streaming responses
  pushNotifications: false # webhook callbacks on long-running tasks
  stateTransitionHistory: true # record task state transitions for replay
```

**`spec.agent` (optional but near-universal).** The LLM the generated agent
defers to. Provider is a fixed enum: `openai`, `anthropic`, `ollama`,
`deepseek`, `google`, `mistral`, `groq`, or `""` for "configure at runtime
via env vars only". Temperature is bounded to 0-2; `maxTokens` must be ≥1.

```yaml
agent:
  provider: deepseek
  model: deepseek-v4-flash
  systemPrompt: |
    You are a helpful A2A agent. Use the AVAILABLE SKILLS playbooks for
    workflows; call tools for deterministic actions.
  maxTokens: 4096
  temperature: 0.3
```

**`spec.card` (optional).** Static fields for the A2A agent card served at
`/.well-known/agent-card.json`. Used by other agents and the Inference
Gateway registry for discovery. Fields are all free-form strings/arrays
(no enum constraints in v1):

```yaml
card:
  protocolVersion: "0.3.0"
  preferredTransport: JSONRPC
  defaultInputModes: [text, voice]
  defaultOutputModes: [text, audio]
  url: "https://my-agent.example.com:8443"
  documentationUrl: "https://github.com/company/my-agent/docs"
  iconUrl: "https://github.com/company/my-agent/icon.png"
```

## Server, auth, and language targets

**`spec.server` (required).** Only `port` is mandatory. `scheme` (`http`/
`https`), `debug`, and `auth.enabled` are optional. v1 only models the auth
on/off toggle - specific providers (OIDC, JWT, ...) live in the generated
code and config sections, not the schema:

```yaml
server:
  port: 8443
  scheme: https
  debug: false
  auth:
    enabled: true
```

**`spec.language` (required).** At least one of three children; the
generator emits the corresponding scaffold. Required pairs per language:

| Language     | Required fields                     | Optional     |
| ------------ | ----------------------------------- | ------------ |
| `go`         | `module`, `version`                 | -            |
| `typescript` | `packageName`, `nodeVersion`        | -            |
| `rust`       | `packageName`, `version`, `edition` | `features[]` |

```yaml
language:
  go:
    module: github.com/example/my-agent
    version: "1.26.2"
```

**`vendor.{deps,devdeps}` (CLI extension, not in v1 schema).** `adl-cli`
reads `spec.language.<lang>.vendor.deps[]` (runtime) and `vendor.devdeps[]`
(code generators / dev tools) for Go and Rust to pre-populate `go.mod` /
`Cargo.toml`. The v1 JSON Schema doesn't validate these keys, but it permits
additional properties so they round-trip cleanly:

```yaml
language:
  go:
    module: github.com/example/my-agent
    version: "1.26.2"
    vendor: # CLI extension
      deps:
        - github.com/stretchr/testify@v1.10.0
      devdeps:
        - golang.org/x/tools/cmd/stringer@v0.20.0
```

Verify against the latest `adl-cli` changelog before relying on it; treat
schema-validated fields as load-bearing and extensions as best-effort.

## Tools vs Skills (the often-confused distinction)

ADL distinguishes two complementary surfaces, and conflating them produces
unmaintainable agents. Apply the rule first, then write the entry.

| Use a **tool** (`spec.tools[]`) when                                              | Use a **skill** (`spec.skills[]`) when                                        |
| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| The agent must invoke a deterministic operation (DB query, HTTP call, file write) | The agent must learn a workflow, policy, or response pattern                  |
| Inputs and outputs are structured (JSON Schema fits)                              | The instructions are prose                                                    |
| Implemented in code                                                               | Authored as markdown                                                          |
| Registered with the toolbox at startup                                            | Loaded into the system prompt at startup via the `AVAILABLE SKILLS:` manifest |

A skill that needs to read files (e.g. its own `SKILL.md` body, or bundled
templates) requires `- id: read` in `spec.tools` _and_
`spec.config.tools.read.enabled: true`. The validator enforces this; don't
disable it.

## Reserved built-in tools

`spec.tools[]` recognises five reserved ids that map to framework-supplied
implementations. They ship with their own unit tests (see
`builtin/*_test.go.tmpl` in the CLI templates), so you do **not** need to
write tests for them. You activate them - that's all:

| Reserved id | Purpose                                                   | Activation namespace      |
| ----------- | --------------------------------------------------------- | ------------------------- |
| `read`      | Read a file (`file_path`, optional `offset`/`limit`)      | `spec.config.tools.read`  |
| `bash`      | Execute a whitelisted shell command with a timeout        | `spec.config.tools.bash`  |
| `write`     | Write content to a file (creates parent dirs)             | `spec.config.tools.write` |
| `edit`      | Replace a unique `old_string` with `new_string` in a file | `spec.config.tools.edit`  |
| `fetch`     | `GET`/`HEAD` an http(s) URL (host whitelist, byte cap)    | `spec.config.tools.fetch` |

All five default to `enabled: false`. Opt in by listing the id alone (no
`name`, `description`, or `schema` - the generator owns those) and setting
`enabled: true` in the matching `spec.config.tools.<id>` block. The reserved
config block accepts only the typed keys the generator knows; typos like
`tymeout_seconds` fail validation by design.

Resolution precedence at runtime is **env > compile-time literal > built-in
default (disabled)**. The kill-switch envs (`A2A_BASH_DISABLED=1`,
`A2A_FETCH_DISABLED=1`, etc.) override the compile-time `enabled: true`.

Each built-in accepts its own typed config keys under
`spec.config.tools.<id>`. Anything not on this list fails validation:

| Tool    | Config keys                                                                                                                                       |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `read`  | `enabled`, `max_lines` (default file slice), `allowed_roots[]` (empty = project-wide)                                                             |
| `bash`  | `enabled`, `whitelist[]` (allowed commands), `timeout_seconds`                                                                                    |
| `write` | `enabled`                                                                                                                                         |
| `edit`  | `enabled`                                                                                                                                         |
| `fetch` | `enabled`, `allowed_domains[]` (entries starting with `.` match any subdomain), `max_bytes`, `timeout_seconds`, `allow_downloads`, `download_dir` |

A representative configuration that opts in the three tools with the
richest config surfaces:

```yaml
config:
  tools:
    read:
      enabled: true
      max_lines: 2000
    bash:
      enabled: true
      whitelist: [ls, cat, grep, find, rg, jq, wc, head, tail, git, go]
      timeout_seconds: 30
    fetch:
      enabled: true
      allowed_domains:
        - pkg.go.dev
        - .rust-lang.org # any subdomain of rust-lang.org
        - raw.githubusercontent.com
      max_bytes: 5242880 # 5 MiB
      timeout_seconds: 20
      allow_downloads: true
      download_dir: /tmp/adl-fetch-cache
tools:
  - id: read
  - id: bash
  - id: fetch
```

## Custom tool implementation

A user tool is a full `spec.tools[]` entry - `id`, `name`, `description`,
optional `tags`, `inject`, and a JSON Schema for `schema`. The generator
produces `tools/<name>.{go,rs,ts}` with:

- a struct holding the injected dependencies (`logger`, services, optional
  `config` or `config.<section>` subsections),
- a constructor `New<PascalName>Tool(...) server.Tool` whose signature mirrors
  `inject:` in declaration order, and
- a handler method `<PascalName>Handler(ctx, args) (string, error)` whose body
  is a single `// TODO: Implement <name> logic` comment plus a placeholder
  return.

You replace the handler body. Do not change:

- the struct name or its field order (regeneration of `main.go` wires fields
  positionally),
- the constructor signature,
- the handler method name.

Injection patterns you can use in `inject:`:

```yaml
inject:
  - logger # always available, *zap.Logger (Go)
  - config # the whole *config.Config
  - config.database # only *config.DatabaseConfig - principle of least privilege
  - cache # any name declared in spec.services
```

Always prefer `config.<section>` over `config` - it keeps the tool's blast
radius small, makes unit tests trivial to set up, and the validator catches
mismatches between `inject:` and `spec.config.*` at generate time.

## Testing custom tools (mandatory)

**The built-in reserved tools (`read`, `bash`, `write`, `edit`, `fetch`) ship
with generated unit tests. Custom tools do NOT.** This is the single most
common gap in ADL projects.

Verification step for every PR that adds or modifies `spec.tools[]`:

1. For each entry whose `id` is _not_ in the reserved set above, confirm a
   sibling test file exists - `tools/<name>_test.go` (Go), `tools/<name>.rs`
   tests block (Rust), `tools/<name>.test.ts` (TypeScript) - and the file is
   listed in `.adl-ignore` so regeneration won't clobber it.
2. The test file MUST exercise the handler against a service mock for every
   dependency in `inject:`. Use the service's interface (declared in
   `spec.services.*.interface`) - that's why it exists.
3. Use table-driven tests (Go) / parameterised tests (Rust) / `describe`+`it`
   blocks (TypeScript) covering at least: happy path, invalid args, service
   error.
4. Run the language-native test command from the generated Taskfile:
   `task test` (Go and Rust) or the equivalent target. CI must pass.

A minimal Go template for a custom tool test (adapt to the actual struct
name and interface):

```go
package tools

import (
    "context"
    "errors"
    "testing"

    "go.uber.org/zap"
    "github.com/stretchr/testify/require"
)

type stubDatabase struct{ err error; out string }

func (s *stubDatabase) Query(ctx context.Context, sql string) (string, error) {
    return s.out, s.err
}

func TestQueryDatabaseTool_Handler(t *testing.T) {
    cases := []struct {
        name    string
        args    map[string]any
        db      *stubDatabase
        wantErr bool
    }{
        {"happy path", map[string]any{"query": "SELECT 1", "table": "t"}, &stubDatabase{out: `{"rows":1}`}, false},
        {"missing query", map[string]any{"table": "t"}, &stubDatabase{}, true},
        {"backend error", map[string]any{"query": "SELECT 1", "table": "t"}, &stubDatabase{err: errors.New("boom")}, true},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            tool := &QueryDatabaseTool{logger: zap.NewNop(), database: tc.db}
            _, err := tool.QueryDatabaseHandler(context.Background(), tc.args)
            if tc.wantErr {
                require.Error(t, err)
            } else {
                require.NoError(t, err)
            }
        })
    }
}
```

Skip this and the agent ships untested business logic - the schema can't
catch a misread `args["query"].(string)` or a swallowed service error.

## The `.adl-ignore` contract

`adl-cli` writes this file on first generation. It works like `.gitignore`
but applies to the _generator_ - anything matched is preserved across
`adl generate --overwrite`. The generator automatically adds:

- every custom tool file (`tools/<name>.<ext>`),
- every custom service implementation (`internal/<service>/*`),
- every `bare: true` skill directory (`skills/<id>/`).

You can extend it to protect anything else you've hand-edited:

```text
# .adl-ignore
Dockerfile          # custom container build
k8s/                # using ArgoCD overlays elsewhere
Taskfile.yml        # extended with project-specific targets
README.md           # written by hand
```

Patterns: `#` for comments, trailing `/` for directories, `*` wildcards,
exact paths.

**When a regeneration goes wrong:** check `.adl-ignore` first. A missing
entry there is the usual cause of "my custom code disappeared after I edited
the manifest."

## Services and configuration

Services are first-class - declare them in `spec.services` with `type`,
`interface`, `factory`, and `description`, and the generator creates
`internal/<service>/<service>.{go,rs}` with an interface stub and a factory
that takes `(*zap.Logger, *config.Config)`.

`type` is a closed enum - pick the one that best names the role so the
generator can emit idiomatic scaffolding:

| `type`       | Use it for                                                  |
| ------------ | ----------------------------------------------------------- |
| `service`    | Domain logic / orchestration (default choice when in doubt) |
| `repository` | Persistence and data-access ports - DBs, object stores      |
| `client`     | Outbound HTTP / gRPC / RPC clients to another system        |
| `middleware` | Request-pipeline behaviour - auth, logging, rate limits     |

`interface` and `factory` must match `^[a-zA-Z][a-zA-Z0-9_]*$` - they
become real identifiers in the generated code. Three rules keep this
maintainable:

1. **One interface per responsibility.** Don't ship a `UtilService`. Split
   `database` and `cache` even if they share a backend.
2. **Inject the interface, never the implementation.** Tools depend on
   `database.DatabaseService`, not on `*database.databaseService`. This is
   what makes the unit-test stubs above trivial.
3. **Configuration mirrors services.** A service named `googleCalendar`
   reads its settings from `spec.config.googleCalendar` -> generated as
   `GoogleCalendarConfig` with `GOOGLE_CALENDAR_*` env prefix. Don't
   hand-wire env vars; let the prefix mapping do it.

## Skills inside an ADL agent

`spec.skills[]` accepts three entry shapes, resolved by `adl-cli`:

| Shape                                                                             | Behaviour                                                                                           |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `id: <name>` (and optional `version: <semver>`)                                   | Fetched from `https://registry.inference-gateway.com/skills/`. Override with `ADL_SKILLS_REGISTRY`. |
| `id: <name>` + `source: <shorthand-or-URL>`                                       | The whole GitHub directory (SKILL.md + any bundled assets) is pulled into `skills/<id>/`.           |
| `id: <name>` + `bare: true` (+ `name`, `description`, `tags`, optional `license`) | Scaffolded locally as a TODO. Author it by hand.                                                    |

`source:` shorthand:

```yaml
- id: skill-creator
  source: skill-creator # inference-gateway/skills, main
- id: skill-creator
  source: skill-creator@v1.0 # pinned tag
- id: pdf
  source: anthropics/skills/pdf # different repo
- id: pdf
  source: anthropics/skills/pdf@abc1234 # pinned commit SHA
- id: custom
  source: https://github.com/my-org/my-repo/tree/release/path/to/skill
```

At runtime, the generated agent walks first-level subdirectories under
`skills/`, parses each `<id>/SKILL.md`'s frontmatter, and appends an
`AVAILABLE SKILLS:` block to the system prompt - the _bodies_ are not
inlined. The model loads them on demand via the `read` tool, so a
skills-using agent must opt `read` in (see "Reserved built-in tools").

`license:` on a skill entry must be one of the SPDX identifiers the schema
accepts (`MIT`, `Apache-2.0`, `BSD-2-Clause`, `BSD-3-Clause`, `GPL-2.0`,
`GPL-3.0`, `LGPL-2.1`, `LGPL-3.0`, `MPL-2.0`, `ISC`, `CC0-1.0`,
`CC-BY-4.0`, `CC-BY-SA-4.0`, `Unlicense`) or the literal `Proprietary`. SPDX
expressions like `MIT OR Apache-2.0` are not currently accepted.

## Local development: sandbox and AI assistants

`spec.development` configures the experience of working _on_ the agent
locally - reproducible dev environments and AI-assistant onboarding files.
Two independent subsections, both optional.

**`spec.development.sandbox`.** Three alternative packagings - pick any
combination; each declares its own `enabled` boolean. Generated artefacts:

| Sub-block       | `enabled: true` generates                                                                      |
| --------------- | ---------------------------------------------------------------------------------------------- |
| `flox`          | `.flox/env/manifest.toml` (Flox/Nix-backed reproducible env)                                   |
| `devcontainer`  | `.devcontainer/devcontainer.json` (VS Code Dev Containers)                                     |
| `dockerCompose` | `docker-compose.yaml` (with the artifacts server wired in when `spec.artifacts.enabled: true`) |

**`spec.development.ai`.** Per-agent toggles for the coding assistants the
project is meant to be edited with. Each is independent; all default off.
The CLI generates each agent's onboarding file the first time it's enabled,
and keeps it in sync on subsequent `adl generate --overwrite` runs unless
you list it in `.adl-ignore`:

| Sub-block    | Generated file                                 |
| ------------ | ---------------------------------------------- |
| `claudecode` | `CLAUDE.md` (Anthropic Claude Code)            |
| `gemini`     | `GEMINI.md` (Google Gemini)                    |
| `codex`      | shared `AGENTS.md` (OpenAI Codex)              |
| `opencode`   | shared `AGENTS.md`                             |
| `infer`      | shared `AGENTS.md` (Inference Gateway `infer`) |

A small example combining sandbox + AI toggles:

```yaml
development:
  sandbox:
    flox:
      enabled: true
    devcontainer:
      enabled: false
    dockerCompose:
      enabled: true
  ai:
    claudecode:
      enabled: true
    codex:
      enabled: true
```

**`spec.development.deps[]` (CLI extension, not in v1 schema).**
`adl-cli` v0.38.0+ reads a cross-cutting list of sandbox-level tool
dependencies to install across every enabled sandbox flavour. Treat it as
a CLI convention - useful, but not schema-validated.

## SCM, CI/CD, and deployment

Two related blocks that together drive everything outside `main.go`: where
the code lives, how it ships, and where it runs.

**`spec.scm`.** All fields optional; the provider enum is closed:

| Field             | Effect                                                                    |
| ----------------- | ------------------------------------------------------------------------- |
| `provider`        | `github` \| `gitlab` \| `bitbucket` - selects the workflow templates      |
| `url`             | Repository URL (used in generated `README.md`, agent card, etc.)          |
| `github_app`      | Generate a GitHub App / token configuration in CI                         |
| `issue_templates` | Write `.github/ISSUE_TEMPLATE/*.md`                                       |
| `dependabot`      | Write `.github/dependabot.yml`                                            |
| `ci`              | Write `.github/workflows/ci.yml`                                          |
| `cd`              | Write `.github/workflows/cd.yml` and `.releaserc.yaml` (semantic-release) |

**`spec.deployment`.** Choose `type: kubernetes` or `type: cloudrun`; the
matching sub-block carries the detail. Both share an `image` shape with
`registry`, `repository`, `tag`, optional `useCloudBuild`.

`type: kubernetes` generates `k8s/deployment.yaml`:

```yaml
deployment:
  type: kubernetes
  kubernetes:
    image:
      registry: ghcr.io
      repository: example/my-agent
      tag: v1.0.0
```

`type: cloudrun` generates `cloudrun/` helpers plus a `deploy` target in
the `Taskfile.yml`. Fields map 1:1 to Cloud Run concepts:

```yaml
deployment:
  type: cloudrun
  cloudrun:
    image:
      registry: gcr.io
      repository: my-agent
      tag: v1.0.0
      useCloudBuild: true
    resources:
      cpu: "1"
      memory: 512Mi
    scaling:
      minInstances: 0
      maxInstances: 100
      concurrency: 1000
    service:
      timeout: 3600
      allowUnauthenticated: true
      serviceAccount: my-agent@PROJECT_ID.iam.gserviceaccount.com
      executionEnvironment: gen2
    environment:
      LOG_LEVEL: info
      ENVIRONMENT: production
```

`adl generate` exposes equivalent CLI flags (`--ci`, `--cd`,
`--deployment kubernetes|cloudrun`, `--flox`, `--devcontainer`) that **OR
with the manifest values**. Prefer the manifest for anything that needs to
be reproducible across machines and CI runs - treat the flags as
one-off escape hatches.

## Artifacts and post-generate hooks

Two small spec blocks that round out the manifest.

**`spec.artifacts.enabled`.** Set `true` to generate an artifacts server -
a small HTTP service for storing task outputs - and to wire the matching
`create_artifact` / `read_artifact` / `list_artifacts` helpers into the
generated code. The backend (filesystem vs MinIO) is chosen at runtime via
env vars on the generated binary, not via the manifest. When
`spec.development.sandbox.dockerCompose.enabled: true`, the MinIO instance
is wired into the generated `docker-compose.yaml` (adl-cli v0.39.0+).

```yaml
artifacts:
  enabled: true
```

**`spec.hooks.post`.** A list of shell commands the CLI runs at the end of
every `adl generate`. Use for the things a generator can't reasonably
infer:

```yaml
hooks:
  post:
    - go mod tidy
    - go generate ./...
```

## Common commands

`adl init` is for bootstrapping a fresh `agent.yaml`; `adl validate` /
`adl generate` are the steady-state loop. Run from the repo root:

```sh
adl init [name]                              # interactive wizard for a new agent.yaml
adl init [name] --defaults                   # non-interactive; accept the defaults
adl validate [file]                          # shape check; default file is agent.yaml
adl generate -f agent.yaml -o .              # initial scaffold
adl generate -f agent.yaml -o . --overwrite  # refresh after manifest edit (respects .adl-ignore)
adl generate ... --offline                   # skip skills registry; require local cache (~/.adl/skills-cache/)
adl generate ... --ci                        # also write .github/workflows/ci.yml
adl generate ... --cd                        # also write cd.yml + .releaserc.yaml
adl generate ... --deployment kubernetes     # also write k8s/deployment.yaml
adl generate ... --deployment cloudrun       # also write cloudrun/ helpers + Taskfile deploy target
adl generate ... --flox                      # enable Flox sandbox (OR with spec.development.sandbox.flox.enabled)
adl generate ... --devcontainer              # enable DevContainer sandbox (OR with spec.development.sandbox.devcontainer.enabled)
adl generate ... -t minimal                  # template selector (currently only "minimal" ships)
```

Once generated:

```sh
task build                                   # compile
task test                                    # run unit tests - INCLUDING custom tool tests
task run                                     # start the agent on spec.server.port
task deploy                                  # only present when spec.deployment is set
```

Validate the manifest in CI on every PR. Add a job step that runs
`adl validate agent.yaml` before any code build - it's faster and surfaces
schema drift early. Keep the manifest as the source of truth - prefer
`spec.scm.ci: true` over `--ci`, `spec.deployment.type: cloudrun` over
`--deployment cloudrun`, and so on. The flags exist for one-off enabling;
they are not a substitute for declarative state.

## Cross-repo awareness

ADL touches several repos in the `inference-gateway` org. When a change in
one of them might ripple, surface it explicitly:

- `inference-gateway/adl` (schema) -> consumers must bump the pinned tag in
  their `Taskfile.yml`/`internal/schema/`. Breaking changes go to `v2/`, not
  on top of `v1/`.
- `inference-gateway/adl-cli` (generator) -> template edits land here, not
  in the schema. A generator change without a schema change means existing
  manifests keep working.
- `inference-gateway/skills` (this catalog) -> if a `spec.skills[]` entry
  uses `source: <id>` without an owner, it resolves here. Pin via `@<tag>`
  for reproducibility.

For ecosystem-wide concerns (release flow, conventional commits,
cross-repo checklists), defer to the `maintainer` skill rather than
restating it.

## What NOT to do

- Don't hand-edit generated files that are _not_ in `.adl-ignore` - they're
  overwritten on the next `adl generate --overwrite`. If you need them
  custom, add them to `.adl-ignore` (and accept that you've forked them
  from the generator).
- Don't rename a custom tool's struct, constructor, or handler. Rename via
  the manifest (`spec.tools[].name`) and regenerate.
- Don't duplicate a reserved built-in id (`read`, `bash`, `write`, `edit`,
  `fetch`) as a custom tool. The generator owns those.
- Don't inject `config` when you only need one section. Use
  `config.<section>` - smaller blast radius, simpler tests.
- Don't make schema-breaking changes in `schema/v1/`. They go to a new
  major (`schema/v2/`) per the additive contract.
- Don't ship a custom tool without a `<name>_test.go` (or language
  equivalent). The reserved built-ins are tested upstream; yours are not.
- Don't conflate ADL with ADK. ADL is the manifest format; the ADK
  (`inference-gateway/adk`) is the Go runtime the generated `main.go`
  imports.
- Don't omit `spec.capabilities`. All three booleans (`streaming`,
  `pushNotifications`, `stateTransitionHistory`) are required by the v1
  schema; `adl validate` rejects the manifest if any are missing.
- Don't hand-edit generated `CLAUDE.md`, `AGENTS.md`, or `GEMINI.md`. They're
  re-emitted by the `spec.development.ai.*` generators on every
  `adl generate --overwrite` unless you add them to `.adl-ignore`. If you
  want the AI-onboarding docs to stay hand-written, ignore the file and
  accept that you've forked it from the generator.

---
> Source: [inference-gateway/skills](https://github.com/inference-gateway/skills) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-15 -->

