Action Inputs V2
This planning document describes what is now Harbor's Action Model.
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, andinput.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 /resourcePATCH /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=mediaGET /drive/v3/files/{fileId}/export?mimeType=...
Without first-class query input support, Harbor is forced into one of two bad choices:
- avoid many useful actions entirely
- 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
- Add first-class support for typed query parameters.
- Preserve Harbor's approval, audit, and policy strengths.
- Keep actions deterministic and inspectable.
- Avoid raw URL templating or generic query-string injection.
- Support future import/codegen from OpenAPI or provider metadata.
- Allow provider-specific computed inputs for advanced APIs like Drive search.
Non-Goals
- This is not a generic scripting system.
- This does not allow arbitrary URL editing at execution time.
- This does not attempt to model every OpenAPI edge case in the first Action Model rollout.
- This does not require Harbor to expose every provider-specific query string directly.
- 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 templatequery: values serialized into the query stringbody: values inserted into the request bodystatic: action-defined values always applied by Harborcomputed: Harbor-generated values derived from structured runtime inputs
Proposed Schema
The following is a suggested manifest shape for http_api actions.
{
"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.
{
"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 namein: one ofpath,query, orbodyrequired: whether the caller must provide a value unless a default existsschema: type and validation rulesdefault: optional Harbor-side defaultdescription: human-readable help textstyle: optional serialization rule, mostly relevant for query paramsexplode: optional serialization behavior for arrays/objectsallowEmptyValue: whether an empty string is permittedsensitive: whether value redaction is needed in logs/UI
Supported Schema Subset for the Action Model
Keep the Action Model intentionally small.
Primitive types
stringbooleanintegernumber
Constrained types
enumminLengthmaxLengthminimummaximumpattern
Simple collections
arrayof 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
{
"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:
quses a provider-specific search grammarfieldsuses 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.
{
"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
{
"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
- Start with the action's
pathTemplate. - Validate and substitute
pathparameters. - Collect
staticQueryvalues. - Collect validated runtime
queryparameters. - Compute any
computedQueryvalues. - Merge query parameters using defined precedence.
- Build request body from
bodyinputs if applicable. - Apply auth and execute.
- 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:
computedQueryoverrides runtimequery- runtime
queryoverridesstaticQuery staticQueryoverrides 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
pathTemplatemust be declared exactly once - every declared
pathparameter 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=bodyis 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
{
"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
responseProfilevalues - 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
{
"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
{
"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
{
"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
{
"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:
{
"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
{
"slug": "get_user",
"method": "GET",
"pathTemplate": "/users/{userId}"
}
Equivalent Action Model
{
"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.
{
"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.parametersin=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
Recommended Minimum Action Model Scope
To keep momentum high, the smallest useful Action Model includes:
inputs.parametersin: path | query- primitive schema validation
defaultstaticQuery- registration-time validation
- audit visibility
That is enough to solve the immediate Drive starter-set gap without overbuilding.
Open Questions
- Should duplicate names across
staticQuery, runtimequery, andcomputedQuerybe forbidden entirely? - Should freeform provider expressions ever be exposed to ordinary agents, or only to admin/operator flows?
- Should
fieldsbe modeled as response profiles only, at least initially? - Should
in=bodyreuse the existing request body system or be redesigned in the same pass? - Should import from OpenAPI/Discovery docs be a near-term goal or a later optimization?
Recommended Decision
Adopt the Action Model with first-class typed query parameters and action-owned request assembly.
Specifically
- add
inputs.parameterswithin=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:
versionis supportedpathTemplateplaceholders 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:
{
"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.mdAction Model
Internal naming ideas
normalizedActionInputsresolvedRequestSpecparameterSpeccomputedQuerySpec
The important part is that "inputs" refers to typed request inputs, while "resolved request" refers to the exact assembled HTTP call.