Skip to main content

Overview

AgentMark supports JSON Schema $ref references in prompt schemas. Instead of duplicating the same schema definition across multiple prompts, you can extract shared definitions into .json files and reference them with $ref. At build time, AgentMark resolves all references and inlines the content. Benefits:
  • DRY schemas: Define a schema once, reuse it across many prompts
  • Easier maintenance: Update a schema in one place, and every prompt that references it picks up the change
Schema references work in both input_schema (input validation) and object_config.schema (structured output).

Basic usage

Create a JSON schema file, then reference it from your prompt’s frontmatter using $ref.

Schema file

schemas/user.json
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "role": { "type": "string", "enum": ["admin", "member", "viewer"] }
  },
  "required": ["name", "email"]
}

Prompt file with $ref

greet-user.prompt.mdx
---
name: greet-user
text_config:
  model_name: gpt-4o
input_schema:
  $ref: ./schemas/user.json
---

<System>You are a friendly assistant.</System>
<User>Greet the user: {props.name} ({props.email}), who has the role {props.role}.</User>
When AgentMark processes this prompt, it loads schemas/user.json and replaces the $ref with the full schema content. The result is identical to having written the schema inline.

Using $ref in output schemas

You can also use $ref in object_config.schema to define the structure of generated objects.
extract-contact.prompt.mdx
---
name: extract-contact
object_config:
  model_name: gpt-4o
  schema:
    $ref: ./schemas/user.json
---

<System>Extract contact information from the following text.</System>
<User>{props.text}</User>

Using $ref for nested properties

You do not need to replace the entire schema with a $ref. You can use $ref for individual properties within a larger schema.
create-order.prompt.mdx
---
name: create-order
object_config:
  model_name: gpt-4o
  schema:
    type: object
    properties:
      customer:
        $ref: ./schemas/user.json
      items:
        type: array
        items:
          $ref: ./schemas/product.json
      total:
        type: number
    required:
      - customer
      - items
---

<System>Create an order from the customer's request.</System>
<User>{props.request}</User>

JSON Pointer fragments

You can reference a specific definition within a schema file using a JSON Pointer fragment (RFC 6901). This is useful when you have a file containing multiple related definitions.

Definitions file

schemas/common.json
{
  "$defs": {
    "Address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zip": { "type": "string" },
        "country": { "type": "string" }
      },
      "required": ["street", "city"]
    },
    "PhoneNumber": {
      "type": "string",
      "pattern": "^\\+[0-9]{1,15}$"
    }
  }
}

Referencing a specific definition

contact-form.prompt.mdx
---
name: contact-form
object_config:
  model_name: gpt-4o
  schema:
    type: object
    properties:
      name:
        type: string
      address:
        $ref: ./schemas/common.json#/$defs/Address
      phone:
        $ref: ./schemas/common.json#/$defs/PhoneNumber
---

<System>Extract contact details from the message.</System>
<User>{props.message}</User>
The #/$defs/Address fragment tells AgentMark to navigate into the JSON object at $defs then Address, and inline only that portion. The fragment follows standard JSON Pointer syntax, so any nested path works (e.g., #/$defs/contact/email).
Older JSON Schema drafts (Draft 4-7) used definitions instead of $defs. Both work with AgentMark since the fragment is just a JSON Pointer path into the file.

Transitive references

Schema files can themselves contain $ref entries that point to other files. AgentMark follows the entire chain and inlines everything.

Example

schemas/user.json
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "address": { "$ref": "./address.json" }
  }
}
schemas/address.json
{
  "type": "object",
  "properties": {
    "street": { "type": "string" },
    "city": { "type": "string" },
    "zip": { "type": "string" }
  }
}
user-profile.prompt.mdx
---
name: user-profile
text_config:
  model_name: gpt-4o
input_schema:
  $ref: ./schemas/user.json
---

<User>Describe the user profile for {props.name}.</User>
AgentMark resolves user.json, then sees the $ref to address.json inside it, resolves that too, and produces a fully inlined schema. Transitive references also work with JSON Pointer fragments — a referenced file can use $ref: ./geo.json#/definitions/Coordinate and it will resolve correctly. AgentMark supports up to 50 levels of transitive references.
Fragment-only references like $ref: "#/$defs/Address" (no file path) are standard JSON Schema internal references. AgentMark preserves these as-is for runtime validation and does not attempt to resolve them.

Security constraints

AgentMark enforces security boundaries on $ref resolution.
  • Local files only: Only relative file paths are supported. Remote URLs like https://example.com/schema.json are not fetched.
  • Project directory boundary: Resolved paths must stay within the project directory. Path traversal attempts like ../../../etc/passwd are rejected by the content loader.
  • No absolute paths: Absolute paths like /etc/passwd are rejected.
  • Sibling properties are dropped: When AgentMark resolves a $ref, the entire object is replaced by the referenced content. Any sibling properties next to $ref (like description) are discarded. Place additional properties in the referenced schema file instead.

Error handling

When a $ref cannot be resolved, AgentMark reports a descriptive error.
ErrorCauseFix
file not found "path"Referenced file does not existCheck the file path is correct and relative to the prompt
"path" is not valid JSONFile contains malformed JSONValidate with a JSON linter
circular reference detected: A -> B -> ATwo or more schemas reference each other in a cycleBreak the cycle by inlining the shared portion
maximum resolution depth (50) exceededReference chain is deeper than 50 levelsSimplify the schema hierarchy
JSON pointer "..." not found in "path"Fragment path does not match any key in the fileCheck the #/... fragment matches the target file structure

Have Questions?

We’re here to help! Choose the best way to reach us: