Documentation

Source-of-truth docs, references, plans, and product material across Harbor surfaces.

Product Surfaces

Action Inputs V2

This planning document describes what is now Harbor's Action Model.

actionsschemainputs
Source: ACTION_INPUTS_V2.md

ACTION_INPUTS_V2.md

This planning document describes what is now Harbor's Action Model.

  • Treat the filename's "v2" as historical planning language.
  • Harbor now presents one Action Model built around input.path, input.query, and input.body.
  • Legacy path-only request envelopes are being phased out in favor of this single model.

Purpose

This proposal extends Harbor's current http_api action model from:

  • fixed paths
  • optional path parameters
  • optional request body

into a safer and more expressive request-input system that supports:

  • path parameters
  • query parameters
  • request body inputs
  • static provider defaults
  • computed provider-specific inputs

The main driver is real-world APIs like Google Drive, where useful read actions depend heavily on query parameters rather than path segments alone.


Problem Statement

The current action model works well for endpoints shaped like:

  • GET /resource/{id}
  • POST /resource
  • PATCH /resource/{id}

It breaks down on APIs where important behavior lives in query parameters, especially when those parameters are:

  • required for correct behavior
  • typed and structured
  • provider-specific
  • dynamic at runtime

Examples from Google Drive include:

  • GET /drive/v3/files?pageSize=...&pageToken=...
  • GET /drive/v3/files?q=...&orderBy=...
  • GET /drive/v3/files/{fileId}?alt=media
  • GET /drive/v3/files/{fileId}/export?mimeType=...

Without first-class query input support, Harbor is forced into one of two bad choices:

  1. avoid many useful actions entirely
  2. add unsafe generic URL or raw query-string escape hatches

This proposal aims to solve that gap without turning Harbor into a freeform HTTP client.


Goals

  1. Add first-class support for typed query parameters.
  2. Preserve Harbor's approval, audit, and policy strengths.
  3. Keep actions deterministic and inspectable.
  4. Avoid raw URL templating or generic query-string injection.
  5. Support future import/codegen from OpenAPI or provider metadata.
  6. Allow provider-specific computed inputs for advanced APIs like Drive search.

Non-Goals

  1. This is not a generic scripting system.
  2. This does not allow arbitrary URL editing at execution time.
  3. This does not attempt to model every OpenAPI edge case in the first Action Model rollout.
  4. This does not require Harbor to expose every provider-specific query string directly.
  5. This does not replace approval gating for risky writes or destructive actions.

Design Principles

1) Structured, not stringly-typed

Inputs should be represented as typed parameters, not concatenated strings.

2) The action owns transport details

The action definition should describe how to build the request. Runtime callers should supply values, not raw URLs.

3) Safe by default

Freeform provider-specific expressions should be minimized or gated.

4) Auditable by design

Audit logs should clearly show:

  • what values were supplied
  • which values were defaulted
  • which values were computed
  • which parameters were omitted

5) Incrementally adoptable

Existing fixed-path actions should continue to work unchanged.


High-Level Model

Introduce an Action Model shape that separates request inputs by location and behavior.

Request input categories

  • path: values substituted into the path template
  • query: values serialized into the query string
  • body: values inserted into the request body
  • static: action-defined values always applied by Harbor
  • computed: Harbor-generated values derived from structured runtime inputs

Proposed Schema

The following is a suggested manifest shape for http_api actions.

json
{
  "kind": "http_api_action",
  "version": 2,
  "slug": "drive_get_file_metadata",
  "title": "Get Google Drive file metadata",
  "description": "Fetch metadata for a specific Drive file.",
  "method": "GET",
  "pathTemplate": "/drive/v3/files/{fileId}",
  "inputs": {
    "parameters": [
      {
        "name": "fileId",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "minLength": 1 },
        "description": "Drive file ID"
      },
      {
        "name": "supportsAllDrives",
        "in": "query",
        "required": false,
        "schema": { "type": "boolean" },
        "default": true,
        "description": "Whether shared drives are supported"
      }
    ],
    "staticQuery": {
      "fields": "id,name,mimeType,parents,modifiedTime,size"
    }
  },
  "result": {
    "mode": "json"
  },
  "approval": {
    "mode": "auto"
  }
}

Parameter Object

Each parameter should follow a consistent shape.

json
{
  "name": "pageSize",
  "in": "query",
  "required": false,
  "schema": {
    "type": "integer",
    "minimum": 1,
    "maximum": 1000
  },
  "default": 100,
  "description": "Maximum number of items to return",
  "style": "form",
  "explode": true,
  "allowEmptyValue": false,
  "sensitive": false
}

Fields

  • name: provider parameter name
  • in: one of path, query, or body
  • required: whether the caller must provide a value unless a default exists
  • schema: type and validation rules
  • default: optional Harbor-side default
  • description: human-readable help text
  • style: optional serialization rule, mostly relevant for query params
  • explode: optional serialization behavior for arrays/objects
  • allowEmptyValue: whether an empty string is permitted
  • sensitive: whether value redaction is needed in logs/UI

Supported Schema Subset for the Action Model

Keep the Action Model intentionally small.

Primitive types

  • string
  • boolean
  • integer
  • number

Constrained types

  • enum
  • minLength
  • maxLength
  • minimum
  • maximum
  • pattern

Simple collections

  • array of primitive items

Deferred for later

  • nested object query serialization
  • polymorphism
  • oneOf/anyOf/allOf
  • complex multipart modeling
  • provider-defined expression languages as general-purpose input types

Static Inputs

Some values should be defined by the action itself and never exposed to the runtime caller.

Why static inputs matter

They allow Harbor to:

  • lock in safe provider defaults
  • reduce prompt surface area
  • produce stable outputs
  • simplify audits

Example

json
{
  "inputs": {
    "parameters": [
      {
        "name": "fileId",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      }
    ],
    "staticQuery": {
      "supportsAllDrives": true,
      "fields": "id,name,mimeType"
    }
  }
}

Computed Inputs

Some providers require query strings that are valid but awkward or unsafe to expose directly.

Google Drive is the best example:

  • q uses a provider-specific search grammar
  • fields uses a provider-specific partial response grammar

Instead of exposing these directly in the default case, Harbor should support computed inputs.

Computed input model

A computed input is derived by Harbor from structured runtime values.

json
{
  "inputs": {
    "parameters": [
      {
        "name": "search",
        "in": "body",
        "required": false,
        "schema": {
          "type": "object",
          "properties": {
            "nameContains": { "type": "string" },
            "mimeType": { "type": "string" },
            "parentId": { "type": "string" },
            "trashed": { "type": "boolean" },
            "modifiedAfter": { "type": "string" }
          }
        },
        "description": "Harbor-structured Drive search input"
      }
    ],
    "computedQuery": [
      {
        "name": "q",
        "provider": "google_drive_search_q_from_structured_input",
        "source": "search"
      }
    ]
  }
}

Benefits

  • safer than raw q
  • easier for agents to use correctly
  • cleaner audits
  • opens the door to cross-provider abstractions later

Response Profiles

Some providers expose highly variable response payloads through parameters like fields.

Rather than exposing raw response-shaping parameters immediately, Harbor should support optional response profiles.

Example

json
{
  "responseProfiles": {
    "basic": {
      "staticQuery": {
        "fields": "files(id,name,mimeType),nextPageToken"
      }
    },
    "detail": {
      "staticQuery": {
        "fields": "files(id,name,mimeType,parents,modifiedTime,size,owners),nextPageToken"
      }
    }
  }
}

Why this helps

  • smaller payloads
  • predictable results
  • easier audits
  • fewer provider-specific strings exposed to callers

Execution Rules

Harbor should build the final request in a deterministic order.

Request assembly order

  1. Start with the action's pathTemplate.
  2. Validate and substitute path parameters.
  3. Collect staticQuery values.
  4. Collect validated runtime query parameters.
  5. Compute any computedQuery values.
  6. Merge query parameters using defined precedence.
  7. Build request body from body inputs if applicable.
  8. Apply auth and execute.
  9. Record the final request shape in the audit trail.

Precedence Rules

To avoid ambiguity, precedence should be explicit.

Proposed precedence

For query values with the same name:

  1. computedQuery overrides runtime query
  2. runtime query overrides staticQuery
  3. staticQuery overrides parameter defaults

Why

  • computed values are usually provider-owned translations
  • runtime inputs should beat default behavior
  • static values should beat implicit defaults

Alternative: disallow duplicate names across sources entirely. That is stricter and simpler. It may be preferable for the initial Action Model.

Recommendation

For the Action Model, reject conflicting definitions at action registration time whenever possible.


Validation Rules

Path parameters

  • every placeholder in pathTemplate must be declared exactly once
  • every declared path parameter must appear in the template
  • path values must be URL-encoded safely

Query parameters

  • unknown query inputs should be rejected
  • values must conform to their schema
  • booleans should serialize consistently as true / false
  • arrays should use explicit style rules

Body inputs

  • the Action Model may continue using the existing body mode if already present
  • if in=body is used for multiple fields, Harbor should merge into one JSON object predictably

Registration-time checks

Reject actions that contain:

  • duplicate parameter names in the same location
  • undeclared path placeholders
  • unsupported schema features
  • invalid default values
  • invalid static/computed conflicts

Audit Model

Audit should capture both human readability and exact execution context.

Suggested audit structure

json
{
  "request": {
    "method": "GET",
    "resolvedPath": "/drive/v3/files/abc123",
    "query": {
      "supportsAllDrives": true,
      "fields": "id,name,mimeType"
    },
    "computed": {
      "q": "name contains 'invoice' and trashed = false"
    }
  },
  "inputs": {
    "supplied": {
      "fileId": "abc123"
    },
    "defaulted": {
      "supportsAllDrives": true
    },
    "omitted": ["pageToken"]
  }
}

Sensitive value handling

Parameters marked sensitive: true should be redacted or partially masked in:

  • UI
  • logs
  • audit records

Policy Hooks

First-class query params make policy much stronger.

Harbor can enforce rules like:

  • disallow certain export MIME types
  • cap pageSize
  • block shared-drive access unless explicitly allowed
  • allow only narrow responseProfile values
  • require approval if an advanced freeform provider query is used

This is much harder with raw query strings.


Drive Examples

Example 1: List files with pagination

json
{
  "kind": "http_api_action",
  "version": 2,
  "slug": "drive_list_files",
  "title": "List Google Drive files",
  "method": "GET",
  "pathTemplate": "/drive/v3/files",
  "inputs": {
    "parameters": [
      {
        "name": "pageSize",
        "in": "query",
        "required": false,
        "schema": { "type": "integer", "minimum": 1, "maximum": 1000 },
        "default": 100
      },
      {
        "name": "pageToken",
        "in": "query",
        "required": false,
        "schema": { "type": "string" }
      },
      {
        "name": "orderBy",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string",
          "enum": ["createdTime desc", "modifiedTime desc", "name"]
        }
      }
    ],
    "staticQuery": {
      "supportsAllDrives": true,
      "includeItemsFromAllDrives": true,
      "fields": "files(id,name,mimeType),nextPageToken"
    }
  },
  "result": { "mode": "json" },
  "approval": { "mode": "auto" }
}

Example 2: List children of a folder

json
{
  "kind": "http_api_action",
  "version": 2,
  "slug": "drive_list_folder_children",
  "title": "List children of a Drive folder",
  "method": "GET",
  "pathTemplate": "/drive/v3/files",
  "inputs": {
    "parameters": [
      {
        "name": "folderId",
        "in": "body",
        "required": true,
        "schema": { "type": "string" }
      },
      {
        "name": "pageToken",
        "in": "query",
        "required": false,
        "schema": { "type": "string" }
      }
    ],
    "computedQuery": [
      {
        "name": "q",
        "provider": "google_drive_children_query",
        "source": "folderId"
      }
    ],
    "staticQuery": {
      "supportsAllDrives": true,
      "includeItemsFromAllDrives": true,
      "fields": "files(id,name,mimeType,parents),nextPageToken"
    }
  },
  "result": { "mode": "json" },
  "approval": { "mode": "auto" }
}

Example 3: Download a blob file

json
{
  "kind": "http_api_action",
  "version": 2,
  "slug": "drive_download_blob_file",
  "title": "Download Google Drive file content",
  "method": "GET",
  "pathTemplate": "/drive/v3/files/{fileId}",
  "inputs": {
    "parameters": [
      {
        "name": "fileId",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      }
    ],
    "staticQuery": {
      "alt": "media",
      "supportsAllDrives": true
    }
  },
  "result": { "mode": "binary" },
  "approval": { "mode": "prompt" }
}

Example 4: Export a Google Doc

json
{
  "kind": "http_api_action",
  "version": 2,
  "slug": "drive_export_google_doc",
  "title": "Export a Google Doc",
  "method": "GET",
  "pathTemplate": "/drive/v3/files/{fileId}/export",
  "inputs": {
    "parameters": [
      {
        "name": "fileId",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      },
      {
        "name": "mimeType",
        "in": "query",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "application/pdf",
            "text/plain",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
          ]
        }
      }
    ]
  },
  "result": { "mode": "binary" },
  "approval": { "mode": "prompt" }
}

Why Not a Raw Query String?

A raw query-string field may look simpler:

json
{
  "queryString": "q=name contains 'x'&pageSize=100"
}

But it creates multiple problems:

  • weak validation
  • fragile agent prompting
  • poorer audit readability
  • weaker policy enforcement
  • awkward escaping/encoding issues
  • poor compatibility with future import/codegen

Recommendation

Do not support raw query-string execution in the default model.

If Harbor ever needs an escape hatch, make it:

  • opt-in
  • admin-only
  • approval-gated
  • clearly labeled advanced mode

Migration Plan

The goal is zero breakage for existing actions.

Existing actions

Current actions can be normalized into the current Action Model shape.

Legacy example

json
{
  "slug": "get_user",
  "method": "GET",
  "pathTemplate": "/users/{userId}"
}

Equivalent Action Model

json
{
  "slug": "get_user",
  "method": "GET",
  "pathTemplate": "/users/{userId}",
  "inputs": {
    "parameters": [
      {
        "name": "userId",
        "in": "path",
        "required": true,
        "schema": { "type": "string" }
      }
    ]
  }
}

Migration strategy options

Option A: dual-version support

  • keep legacy runtime support
  • add the Action Model alongside it
  • gradually migrate manifests

Option B: normalization at load time

  • accept both legacy and current manifests
  • normalize to a single internal Action Model representation
  • execute only the normalized form

Recommendation

Use Option B.

It keeps runtime behavior unified and reduces branching in execution code.


Suggested Internal Normalization Shape

Even if the external manifest has compatibility modes, Harbor should normalize every action into one internal execution model.

json
{
  "slug": "get_user",
  "method": "GET",
  "pathTemplate": "/users/{userId}",
  "parameters": [
    {
      "name": "userId",
      "in": "path",
      "required": true,
      "schema": { "type": "string" }
    }
  ],
  "staticQuery": {},
  "computedQuery": [],
  "bodyModel": null
}

Rollout Plan

Phase 1: Core query support

Implement:

  • inputs.parameters
  • in=path|query
  • primitive schema validation
  • staticQuery
  • registration-time validation
  • audit support

This unlocks most safe Drive read actions.

Phase 2: Better body support

Implement:

  • improved JSON body modeling
  • in=body
  • predictable merge semantics

Phase 3: Computed provider inputs

Implement:

  • computedQuery
  • provider adapters for Google Drive q
  • optional response profiles

Phase 4: Advanced mode

Consider, but do not require:

  • admin-only raw provider expressions
  • stronger policy hooks
  • import/codegen from OpenAPI or Discovery docs

To keep momentum high, the smallest useful Action Model includes:

  • inputs.parameters
  • in: path | query
  • primitive schema validation
  • default
  • staticQuery
  • registration-time validation
  • audit visibility

That is enough to solve the immediate Drive starter-set gap without overbuilding.


Open Questions

  1. Should duplicate names across staticQuery, runtime query, and computedQuery be forbidden entirely?
  2. Should freeform provider expressions ever be exposed to ordinary agents, or only to admin/operator flows?
  3. Should fields be modeled as response profiles only, at least initially?
  4. Should in=body reuse the existing request body system or be redesigned in the same pass?
  5. Should import from OpenAPI/Discovery docs be a near-term goal or a later optimization?

Adopt the Action Model with first-class typed query parameters and action-owned request assembly.

Specifically

  • add inputs.parameters with in=path|query|body
  • support staticQuery
  • add strict validation and normalization
  • keep raw query strings out of the default model
  • add computed provider inputs later for cases like Drive q

This solves the current Google Drive dynamic input problem while preserving Harbor's core strengths:

  • safety
  • auditability
  • approval control
  • manifest clarity
  • future extensibility

Appendix A: Example Registration Validation Checklist

At action registration time, validate that:

  • version is supported
  • pathTemplate placeholders match declared path params exactly
  • parameter names are unique per location
  • defaults conform to schema
  • static query keys do not collide with computed query keys
  • unsupported schema features are rejected early
  • required parameters do not conflict with immutable static values
  • result mode is compatible with method and action design

Appendix B: Example Runtime Input Envelope

A possible runtime input payload for an Action Model action:

json
{
  "inputs": {
    "fileId": "abc123",
    "mimeType": "application/pdf"
  }
}

Harbor would map that against the action definition, validate the values, build the request, and record the resolved request in audit.


Appendix C: Suggested Naming

External naming

  • ACTION_INPUTS_V2.md
  • Action Model

Internal naming ideas

  • normalizedActionInputs
  • resolvedRequestSpec
  • parameterSpec
  • computedQuerySpec

The important part is that "inputs" refers to typed request inputs, while "resolved request" refers to the exact assembled HTTP call.