Source: docs/integration/capability-handoff.md

Self-service business capabilities

> New to capabilities? Start with the Capability quickstart

> for a step-by-step guide to your first read capability.

Use this document when your application's chat needs read-only live data or a

bounded preparation result from your application. An integrator can validate,

publish, and manage capabilities through the Public API with its API key.

Mental model for an AI agent

Treat each capability as a typed tool backed by an endpoint in the host

application. Forgium selects and invokes the tool; the host endpoint remains

the authority for business data, authorization, and side effects.

Before choosing a capability, an agent should:

  1. Read the capability's description and when_to_use as routing metadata.
  2. Read input_schema and identify every field in required.
  3. Distinguish optional properties from alternative required inputs. For
    example, oneOf can require exactly one selector such as item_id or
    email.
  4. Use values from the user or a validated capability result. Never invent an
    identifier, URL, price, status, or missing business value.
  5. Expect one capability call per turn. If the next operation depends on a
    user choice, stop and ask the host application to continue the flow.

The endpoint—not the model—decides whether an input identifies a resource and

whether an operation is authorized.

Image-assisted capabilities

An external host can send an image through the HTTP channel, but structured

extraction only happens when an active capability declares

image_interpretation. The image is not returned as raw OCR data. Forgium

extracts the declared fields, validates the complete input_schema, and sends

the resulting arguments to the host capability endpoint.

The declaration is partial: omit required from extraction_schema. The

capability's normal input_schema.required remains authoritative, so Forgium

asks for missing fields instead of inventing them.

{
  "name": "prepare_expense_from_invoice",
  "description": "Prepares an expense lookup or host-owned operation from an invoice.",
  "when_to_use": "Use when the user asks to register or inspect a purchase invoice.",
  "input_schema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["supplier", "invoice_number", "total"],
    "properties": {
      "supplier": { "type": "string", "maxLength": 200 },
      "invoice_number": { "type": "string", "maxLength": 100 },
      "total": { "type": "number" },
      "issued_at": { "type": "string", "maxLength": 40 },
      "currency": { "type": "string", "maxLength": 3 }
    }
  },
  "image_interpretation": {
    "document_kinds": ["invoice", "receipt"],
    "extraction_schema": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "supplier": {
          "type": "string",
          "maxLength": 200,
          "description": "Supplier or issuer name shown on the document."
        },
        "invoice_number": {
          "type": "string",
          "maxLength": 100,
          "description": "Invoice or fiscal document number."
        },
        "total": {
          "type": "number",
          "description": "Final document total."
        },
        "issued_at": {
          "type": "string",
          "maxLength": 40,
          "description": "Issue date shown on the document."
        },
        "currency": {
          "type": "string",
          "maxLength": 3,
          "description": "Currency code shown on the document."
        }
      }
    }
  }
}

The host must still provide the remaining required capability fields, including

the HTTPS endpoint, authentication, output schema, side_effect: "read",

timeouts, and retry policy. image_interpretation does not make Forgium an

accounting system: the host endpoint remains responsible for business rules,

authorization, persistence, and any eventual operation.

Supported initial document kinds are bank_transfer_receipt, invoice, and

receipt. PDFs, multiple images, audio, and video are not part of this public

contract.

Image to confirmation to host execution

For a mutation-owned workflow such as registering an expense or initiating a

transfer, the image capability is a preparation capability. The complete

flow is:

user image
  -> Forgium OCR and schema extraction
  -> host preparation endpoint receives validated arguments
  -> host returns confirmation_required with a preview
  -> Forgium returns the chat response and host-owned interaction
  -> host UI asks the user to confirm
  -> host executes the mutation

Forgium does not execute the final mutation, authorize it, persist the original

image, or expose raw OCR. The host endpoint remains responsible for business

validation, authorization, idempotency, persistence, and execution. To expose

this flow, the capability must use side_effect: "read",

result_handling: "host_interaction", and the canonical confirmation output

schema. The preview should include the values extracted from the image so the

user can verify them before confirming.

The same boundary applies to invoices and bank-transfer receipts: Forgium can

extract and prepare the operation, but the host owns the eventual mutation.

Capabilities may optionally declare context_bindings for a required

name-like input. A binding identifies the input property, accepted validated

reference fields, and producer capability names. It makes provenance explicit

for deterministic contextual recovery without making the previous capability

a hard workflow prerequisite. The metadata is advisory routing information;

account authorization, revision checks, and input validation still happen at

the executor boundary.

Required and optional inputs

For an update capability, make the canonical identifier mandatory and keep

fields that may be changed optional. Use a schema composition rule when the

caller may provide one of several selectors:

{
  "type": "object",
  "additionalProperties": false,
  "required": ["changes"],
  "properties": {
    "item_id": { "type": "string", "maxLength": 128 },
    "email": { "type": "string", "maxLength": 320 },
    "changes": {
      "type": "object",
      "additionalProperties": false,
      "required": ["display_name"],
      "properties": {
        "display_name": { "type": "string", "maxLength": 200 },
        "status": { "type": "string", "maxLength": 40 }
      }
    }
  },
  "oneOf": [
    { "required": ["item_id"] },
    { "required": ["email"] }
  ]
}

This accepts either the canonical item_id or the alternative email, but

not both and not neither. The changes object is required, while its

individual properties can be optional according to the host application's

PATCH semantics. If an update must contain at least one change, declare that

with bounded schema composition rather than relying on a prompt or business

rule in prose.

Lifecycle

Implement endpoint + scoped token → test endpoint → expose HTTPS URL → encrypt token in vault
→ import (validated and active) → contract test → verify chat response

The API key determines which capabilities the request can manage. No account or

organization identifier is required in the manifest or URL.

Capability IDs and revisions

Each imported capability receives its own immutable id and independent

revision sequence. Revisions are not shared by the capabilities belonging to

the same client or manifest:

cap_people: import (1, active) → test (1) → verify chat
cap_orders: import (1, active) → test (1) → verify chat

Import records the first active revision. Persist the returned { id, revision }

pair for each capability. A host endpoint that validates the signed

Forgium-Context, or a chat smoke test configured to target one capability,

must use the current revision rather than assume that it remains unchanged.

Disable and enable are explicit revisioned operations. Both require

If-Match; enabling also rechecks the configured credential. A PATCH validates

and publishes the complete replacement atomically, preserves active or

disabled, and increments the same capability's revision. Capability IDs are

immutable: updating a definition creates a new revision under the same ID, not

a new resource or an automatic reference migration.

If one manifest imports multiple capabilities, contract-test every returned ID

separately. One capability's update, enable, or disable operation never changes

another capability's revision. Keep a separate configuration value per

capability when the host application pins revisions, for example:

FORGIUM_PEOPLE_CAPABILITY_ID=cap_...
FORGIUM_PEOPLE_CAPABILITY_REVISION=2
FORGIUM_ORDERS_CAPABILITY_ID=cap_...
FORGIUM_ORDERS_CAPABILITY_REVISION=2

Endpoint requirements

Your endpoint must:

A local JSON-backed endpoint is valid for development, but localhost cannot

be called by deployed Workers. Test it locally first, then use an authorized

HTTPS tunnel for a development test or deploy it through the application's

authorized deployment path. Do not use an example or placeholder URL.

Host-managed interactions

Contract 5 capabilities are read-only. A host application that needs to

continue an operation can expose a preparation capability with

result_handling: "host_interaction". Forgium validates the bounded result and

returns it as interaction; it never executes, authorizes, selects, or retries

the operation.

The preparation call is a just-in-time preflight, not a request for the host to

predict a future user action. Forgium calls the operation-specific preparation

capability after the user expresses the intent. The host then resolves the

referenced resource and checks whether that operation is currently eligible.

The preparation endpoint returns exactly one of these shapes:

Confirmation required

~~~json

{

"status": "confirmation_required",

"confirmation": {

"operation": "cancel_expense",

"operation_ref": "opaque-host-value",

"expires_at": "2026-09-01T12:30:00Z",

"preview": {

"title": "Cancelar gasto",

"fields": [{ "label": "Concepto", "value": "Servicio de internet" }]

}

}

}

~~~

Selection required

~~~json

{

"status": "selection_required",

"selection": {

"operation": "cancel_expense",

"selection_ref": "opaque-host-selection",

"expires_at": "2026-09-01T12:30:00Z",

"options": [

{ "option_ref": "opaque-option-1", "label": "Internet — Agosto — $45.000" },

{ "option_ref": "opaque-option-2", "label": "Internet — Julio — $42.000" }

]

}

}

~~~

options contains between two and eight unique opaque option references. The

host bounds the candidates before responding and Forgium validates the count,

uniqueness, text, expiry, and closed shape before returning anything.

Not found

~~~json

{

"status": "not_found"

}

~~~

not_found admits no other field. It means that the preparation endpoint found

no operation currently eligible for this request. This includes both cases:

The status does not assert that the underlying resource is absent and does not

distinguish these causes. Do not add a reason or host-authored message: the

branch is exactly { "status": "not_found" }. Do not use it for ambiguity;

return selection_required instead.

Semantic resolution is the host's responsibility

Forgium validates the response shape, bounds, expiry, and opaque-reference

rules. It cannot inspect the host's business data and cannot determine whether

the host classified a result correctly. The host does not need to anticipate

the request: it performs this work when Forgium invokes the preparation

endpoint. At that point the host must resolve matching resources, apply the

requested operation's current-state preconditions, and then classify the

eligible set:

Result after host resolution and eligibility checksRequired preparation responseHost responsibility
No matching resourcenot_foundConfirm that no operation is available for the authenticated subject and request.
Matching resource(s), but 0 eligible operationsnot_foundApply current-state preconditions; do not offer an invalid operation.
1confirmation_requiredCreate one subject-scoped operation reference and preview.
2–8selection_requiredReturn every candidate the user must distinguish, with unique option references and labels.
More than 8selection_required with a host-defined bounded candidate setApply a deterministic domain rule to bound the candidates; never report not_found merely because the result is ambiguous or too large.

There is no fallback branch in which ambiguity is represented as absence. A

host must not return not_found for a query such as “el gasto del ascensor”

when multiple eligible expenses match. Forgium will accept and render a

well-formed but semantically incorrect not_found response, so this rule must

be enforced and tested in the host adapter. Model routing is not a substitute

for this resolution: if the model does not call the preparation capability,

Forgium does not synthesize not_found from that omission.

business_rules can help the routing model decide when to call a capability,

but it is not an authorization or state-validation mechanism and it does not

run after the endpoint chooses a result branch. Eligibility must therefore be

enforced by deterministic host code, not by model instructions.

Validate twice: preparation and mutation

Preparation is a user-experience and safety preflight, not a lock or permission

to mutate. If the host returns confirmation_required, the represented

resource can change before the user confirms. When the host later receives the

confirmation, it must validate the operation again against the current subject,

authorization, resource state, revision or fingerprint, and idempotency rules.

If any precondition no longer holds, the host must block the mutation. A

successful preparation response must never bypass the mutation endpoint's

normal business invariants.

All opaque references contain 1–512 plain-text characters. operation is a

lowercase machine name of 2–64 characters. Expiry is a future RFC 3339 value no

more than 30 minutes ahead. A preview has a 1–120 character title and 1–8

label/value fields; labels are at most 80 characters and values at most 500.

Option labels are at most 200 characters. The complete result is at most 8 KiB.

The HTTP response contains a closed interaction union with

owner: "host". The host UI owns selection or confirmation and the host

backend must perform authorization, concurrency, idempotency, and mutation.

Opaque references are returned only in the interaction response; they are not

sent to Workers AI, conversation history, message metadata, traces, or Forgium

persistence. A later response never reconstructs or resends a previous

interaction. Forgium persists only its own interaction_id, operation name,

scope, type, status, and timestamps for terminal-report correlation. The host

may optionally report the terminal status through

POST /v1/conversations/{conversationId}/operation-results.

For not_found, Forgium returns the neutral deterministic assistant text “No

hay una operación disponible para esta consulta.” The wording intentionally

covers both absence and current-state ineligibility without claiming that the

underlying resource does not exist. Forgium does not send the preparation

result to Workers AI, accept host-authored prose, or return a public

interaction object for that branch.

Invocation wire contract

For an HTTP-channel message, Forgium preserves the opaque data.user_id as

subject_id and preserves the request conversation_id (or the generated

conversation ID). The request scope comes from the authenticated Forgium API key,

never from caller-supplied scope data. The capability endpoint receives this

context in the Forgium-Context header; it is not a user-controlled header.

A redacted read invocation looks like this:

POST https://consumer.example/v1/people/lookup HTTP/1.1
Authorization: Bearer <scoped-endpoint-token>
Accept: application/json
Content-Type: application/json
Forgium-Context: <compact-Ed25519-JWS-redacted>

{"name":"Juan Pérez"}

A host-owned operation is prepared through a read-only capability and carries

no mutation or approval headers. Its terminal outcome is completed by the host

application, not by Forgium:

POST https://consumer.example/v1/host-interactions/prepare HTTP/1.1
Authorization: Bearer <scoped-endpoint-token>
Accept: application/json
Content-Type: application/json
Forgium-Context: <compact-Ed25519-JWS-redacted>

{"query":"cancelar el gasto de internet"}

The compact JWS protected header is `{ "alg": "EdDSA", "kid": "...",

"typ": "forgium-context+jwt" }. Its payload contains exactly iss, aud`,

iat, exp, jti, the authenticated request-scope claim, subject_id,

conversation_id, capability_id, revision, method, url, and

body_sha256. The endpoint

must fetch the public key from

/.well-known/forgium/capability-context/v1/jwks.json, verify the signature,

issuer, audience, kid, method, exact URL, time window, one-time jti, and the

SHA-256 hash of the raw body. exp is five minutes after iat; allow at most

60 seconds of clock skew. Complete JWS values and bodies must not be logged.

For person lookup, prefer one read-only endpoint:

POST https://api.example.com/v1/people/lookup
Content-Type: application/json

{"name":"Juan Pérez"}
{
  "person_id": "person_123",
  "name": "Juan Pérez",
  "email": "juan@example.com"
}

Create <project-root>/forgium-agent.capabilities.json

Use the real endpoint URL. Every endpoint must be protected with a scoped

token. Register the token in the vault first; never put a token or private

header in the manifest.

{
  "domain": "people",
  "capabilities": [
    {
      "name": "lookup_person",
      "description": "Looks up a person's approved contact details by name or DNI.",
      "when_to_use": "Use when the user asks for a person's email, contact details, or data by DNI.",
      "method": "POST",
      "url": "https://your-reachable-host.example/v1/people/lookup",
      "auth": { "type": "bearer", "credential_ref": "cred_people_api" },
      "safe_headers": {
        "accept": "application/json",
        "content-type": "application/json"
      },
      "input_schema": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "maxLength": 200 },
          "dni": { "type": "string", "maxLength": 20 }
        }
      },
      "output_schema": {
        "type": "object",
        "additionalProperties": false,
        "required": ["person_id", "name"],
        "properties": {
          "person_id": { "type": "string", "maxLength": 100 },
          "name": { "type": "string", "maxLength": 200 },
          "email": { "type": "string", "maxLength": 254 },
          "phone": { "type": "string", "maxLength": 50 }
        }
      },
      "business_rules": ["The backend is authoritative for person data."],
      "examples": [],
      "side_effect": "read",
      "requires_approval": false,
      "timeout_ms": 5000,
      "retry_count": 1
    }
  ]
}

Every model-visible string needs maxLength; arrays need maxItems; objects

need additionalProperties: false.

For an input that requires exactly one of two selectors, use oneOf with a

required branch for each selector. anyOf and not are also supported in

input_schema if you need the equivalent “at least one, but not both” form.

Use pattern only for a bounded string argument format; endpoint validation

and authorization remain mandatory. These keywords are not supported in

output_schema; see the capability quickstart

for the exact supported-keyword matrix and examples.

Register, test, and manage state

You can create a capability in two ways: single (POST /v1/capabilities)

for one capability, or import (POST /v1/capabilities/import) for bulk

creation. Both validate the complete manifest and publish an active revision.

The import response includes lifecycle_status: "active" and revision: 1

for each returned capability.

Single capability (recommended for one capability)

CREATE_RESPONSE="$(curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities" \
  -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: capability-create-$(date +%s)" \
  -d '{
    "domain": "people",
    "capability": {
      "name": "lookup_person",
      "description": "Looks up a person by name or DNI.",
      "when_to_use": "Use when the user asks for a person email, contact details, or data by DNI.",
      "method": "POST",
      "url": "https://your-reachable-host.example/v1/people/lookup",
      "auth": { "type": "bearer", "credential_ref": "cred_people_api" },
      "safe_headers": { "accept": "application/json", "content-type": "application/json" },
      "input_schema": {
        "type": "object", "additionalProperties": false,
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "maxLength": 200 },
          "dni": { "type": "string", "maxLength": 20 }
        }
      },
      "output_schema": {
        "type": "object", "additionalProperties": false,
        "required": ["person_id", "name"],
        "properties": {
          "person_id": { "type": "string", "maxLength": 100 },
          "name": { "type": "string", "maxLength": 200 },
          "email": { "type": "string", "maxLength": 254 },
          "phone": { "type": "string", "maxLength": 50 }
        }
      },
      "business_rules": ["The backend is authoritative for person data."],
      "examples": [],
      "side_effect": "read",
      "requires_approval": false,
      "timeout_ms": 5000,
      "retry_count": 1
    }
  }')"
CAPABILITY_ID="$(jq -er '.id' <<< "$CREATE_RESPONSE")"

Bulk import (multiple capabilities at once)

export CAPABILITY_ID=""

# Store only the endpoint token from the host secret manager.
# This request stores only AES-GCM ciphertext in the credential vault.
curl -fsS -X PUT "$FORGIUM_AGENT_BASE_URL/v1/capability-credentials/cred_people_api" \
  -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(printf '{\"auth_type\":\"bearer\",\"secret\":%s}' "$(jq -Rn --arg value "$CAPABILITY_ENDPOINT_TOKEN" '$value')")"

IMPORT_RESPONSE="$(curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/import" \
  -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: capability-import-$(date +%s)" \
  --data-binary @forgium-agent.capabilities.json)"

CAPABILITY_ID="$(jq -er '.capabilities[0].id' <<< "$IMPORT_RESPONSE")"
TEST_RESPONSE="$(curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/$CAPABILITY_ID/test" \
  -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \
  -H "If-Match: 1" \
  -H "Content-Type: application/json" \
  -d '{"arguments":{"name":"Juan Pérez"}}')"
CAPABILITY_REVISION="$(jq -er '.revision' <<< "$TEST_RESPONSE")"

# Import has already published revision 1. Use the tested revision in a host
# application's smoke-test configuration.
printf 'FORGIUM_CAPABILITY_REVISION=%s\n' "$CAPABILITY_REVISION"

The contract test calls the real active endpoint at the supplied revision.

Its signed Forgium-Context uses deterministic test scope:

subject_id      = forgium-contract-test
conversation_id = contract-test:{capabilityId}

There is no declarative expect block in contract 5. A test passes when the

request satisfies the input contract and the endpoint returns any valid output

branch. For host_interaction, confirmation_required, selection_required,

and not_found can all pass and are checked by the same closed parser used at

runtime. Use fixture arguments that return not_found, or isolate any temporary

preparation records under the test scope with a short TTL. A preparation record

is allowed; executing a business mutation is not.

Useful routes:

OperationRoute
List own capabilitiesGET /v1/capabilities
Create and publish one capabilityPOST /v1/capabilities
Validate and publish capabilitiesPOST /v1/capabilities/import
Get or publish a replacement revisionGET / PATCH /v1/capabilities/{capabilityId}
Contract testPOST /v1/capabilities/{capabilityId}/test
Enable or disablePOST /v1/capabilities/{capabilityId}/enable or /disable

Read-only capability contract

Contract 5 rejects any capability whose side_effect is not read or whose

requires_approval flag is true. Do not register a mutation endpoint in a public

capability manifest. Keep writes and external sends in the host application.

A capability that prepares a host-owned operation may declare:

Canonical host-interaction output schema

The following output_schema is required. It is one explicit sanitization

shape because composition keywords are not supported for capability outputs;

Forgium additionally enforces the discriminated status/branch relationship.

The complete property structure and status enum are mandatory; an integrator

may tighten, but never widen, the published maxLength and maxItems bounds.

{
  "type": "object",
  "additionalProperties": false,
  "required": ["status"],
  "properties": {
    "status": { "type": "string", "maxLength": 21, "enum": ["confirmation_required", "selection_required", "not_found"] },
    "confirmation": {
      "type": "object", "additionalProperties": false,
      "required": ["operation", "operation_ref", "expires_at", "preview"],
      "properties": {
        "operation": { "type": "string", "maxLength": 64 },
        "operation_ref": { "type": "string", "maxLength": 512 },
        "expires_at": { "type": "string", "maxLength": 35 },
        "preview": {
          "type": "object", "additionalProperties": false,
          "required": ["title", "fields"],
          "properties": {
            "title": { "type": "string", "maxLength": 120 },
            "fields": {
              "type": "array", "maxItems": 8,
              "items": {
                "type": "object", "additionalProperties": false,
                "required": ["label", "value"],
                "properties": {
                  "label": { "type": "string", "maxLength": 80 },
                  "value": { "type": "string", "maxLength": 500 }
                }
              }
            }
          }
        }
      }
    },
    "selection": {
      "type": "object", "additionalProperties": false,
      "required": ["operation", "selection_ref", "expires_at", "options"],
      "properties": {
        "operation": { "type": "string", "maxLength": 64 },
        "selection_ref": { "type": "string", "maxLength": 512 },
        "expires_at": { "type": "string", "maxLength": 35 },
        "options": {
          "type": "array", "maxItems": 8,
          "items": {
            "type": "object", "additionalProperties": false,
            "required": ["option_ref", "label"],
            "properties": {
              "option_ref": { "type": "string", "maxLength": 512 },
              "label": { "type": "string", "maxLength": 200 }
            }
          }
        }
      }
    }
  }
}

The endpoint must return a result matching the closed host-interaction schema.

The host remains responsible for presenting confirmation or selection, checking

authorization and current state, and performing the eventual mutation. Use a

short expiration in the host operation reference and implement replay and

idempotency controls in the host system.

Common errors

ErrorCauseResolution
CREDENTIAL_NOT_CONFIGUREDThe referenced credential has not been provisioned.Configure credential_ref through PUT /v1/capability-credentials/{credentialRef} before import, update, or enable.
HOST_INTERACTION_INVALIDThe preparation endpoint returned a shape outside the closed interaction contract.Return exactly one of the documented preparation statuses and respect all bounds.
CAPABILITY_DISABLEDThe capability is disabled.Re-enable it explicitly with POST /v1/capabilities/{capabilityId}/enable and the current If-Match revision.
REVISION_CONFLICTIf-Match does not match the current revision.Re-read the capability and retry with the current revision; never overwrite a concurrent update.
MUTATIONS_NOT_SUPPORTED_IN_CONTRACT_5The manifest declares a mutating capability.Replace it with a read-only preparation capability and keep the mutation in the host application.

At runtime, the HTTP-channel response can include an interaction object owned

by the host. The host application must route by interaction.type, keep its

opaque references private to the host flow, and perform the operation itself.

It may report only the terminal status through the operation-results callback;

Forgium never treats a conversational confirmation as authorization.

interaction_id is a Forgium-generated unique correlation identifier. It is

stable for the life of one interaction and has state scoped to account,

conversation, and subject. The host-provided expires_at is the authoritative

reporting deadline, subject to the 30-minute maximum: reports are accepted only

while the interaction is pending and Forgium's clock is before that timestamp.

Correlation metadata may remain under the documented 90-day retention policy,

but retention never extends the operational or reporting window.

Credentials

Provision the endpoint token through PUT /v1/capability-credentials/{credentialRef}

before importing its manifest. The secret is AES-GCM ciphertext at rest; it is

never returned by the API.

auth.typeRequired request fields
bearerauth_type: "bearer", secret
api_keyauth_type: "api_key", header_name: "x-api-key" or "x-api-token", secret
basicauth_type: "basic", secret containing the pre-encoded Basic value

auth.type: "none" is rejected for self-service imports. Do not leave a

business endpoint open just to make a capability work.

Current-run capability diagnostics

The synchronous HTTP-channel response may include capability_trace and

benchmark_observation for the run it has just completed. These optional

objects let a host distinguish a capability that was not selected, rejected at

schema validation, failed, returned an empty result, or produced a final

response.

capability_trace contains at most eight ordered, sanitized events. It can

include a capability name, revision, stable status, and reason code; it never

contains prompts, model reasoning, message text, arguments, result bodies,

credentials, signed context, headers, or hashes. benchmark_observation is a

compact projection for the published reliability benchmark and contains no

argument values or result bodies.

These fields describe the current run only. They are not a historical trace

search API and must not drive authorization, approvals, retries, or execution.

Reading capability_trace

The events are ordered by ordinal. They describe observable runtime stages,

not model reasoning or an explanation of intent.

StatusMeaningWas the capability endpoint called?
not_selectedThe selection inference returned no usable capability call.No.
unresolvedThe selection inference emitted the bounded unresolved-request signal. Its reason is not authoritative.No.
selectedA candidate capability and its displayed revision were selected; input validation follows.Not yet.
arguments_invalidThe selected call failed the capability input schema before execution.No.
execution_failedThe executor could not complete a valid selected read.It may have been contacted; inspect reason_code.
empty_resultThe sanitized result was structurally empty, for example null, [], or { "items": [] }.Yes.
result_receivedThe executor returned a non-empty result that passed its output contract.Yes.
response_generatedThe turn reached an answer, approval, or safe fallback terminal path.Depends on preceding events.
runtime_failedWorkers AI could not complete the indicated inference stage after the configured retries.Depends on preceding events.

reason_code values are deliberately bounded and contain neither provider

messages nor customer data:

CodeSemantics
no_tool_callThe selection response had no function call. It does not prove missing context, bad configuration, or an intent classification.
no_active_capabilitiesNo active, eligible capability was loaded for the run.
policy_out_of_scopeThe selected tool was not in the authenticated account's eligible tool set.
coverage_gap, out_of_scope, agent_dead_endBounded unresolved-request signals emitted by the selection model. They are operational hints, not authoritative intent or authorization decisions.
schema_required_missing, schema_invalidThe selected arguments failed the input schema before the executor was called. The trace does not reveal the field or value.
input_invalid, capability_not_active, approval_requiredThe executor rejected the request's current capability state or validated input.
upstream_timeout, upstream_unavailable, upstream_http_errorThe executor could not obtain a usable upstream response.
response_invalidThe upstream response could not satisfy the capability response contract, including malformed, oversized, or schema-invalid content.
structurally_emptyThe executor returned a successful sanitized result with no structural values. It is not an execution error.
groundedA non-empty read result was followed by a completed grounded response, or an empty result was answered deterministically.
answer_onlyThe turn finished without a capability call.
host_interactionA read-only preparation result requires confirmation or selection. A valid not_found branch is handled deterministically as empty_result/structurally_empty; it is not host-configurable prose or a public interaction.
safe_fallbackThe user-facing response took a bounded safe fallback. Read the preceding event for the causal category. This code alone is not a root cause.
selection_inference_failed, grounded_inference_failedWorkers AI failed, respectively, before selecting a tool or while generating the answer from a successful non-empty result. These are paired with runtime_failed.
run_deadline_exceededThe run exceeded its bounded execution deadline.

Selection and grounded-generation criteria

A capability is eligible for selection only when it belongs to the API-key

account, is active, allows the current channel ("http" or "*" for the

HTTP channel), has no unavailable feature flag, and is within the runtime's

bounded candidate set. The selection model receives its name, description,

when_to_use, and input schema; it may select at most one capability per

turn. A passing contract test validates endpoint execution, not model

selection.

Grounded generation happens only after a selected read passes input validation

and returns a non-empty result accepted by the executor's output contract. It

uses that sanitized result without further tool calls. Therefore,

result_received followed by runtime_failed with

grounded_inference_failed means the capability completed but the second

inference did not; it is not an endpoint schema or authorization failure.

Before argument validation or executor I/O, the runtime also applies explicit

exclusion clauses declared by the capability. A matching Do not use for ...

term produces selected followed by unresolved with coverage_gap and a

safe_fallback; no capability request is sent. These clauses are fail-closed

routing constraints only and never authorize a capability or override account

policy.

Diagnostic checklist

  1. Save the run_id, message_id, event sequence, model, and timing from the
    same HTTP response.
  2. For a capability that was not called, first verify lifecycle, account,
    allowed_channels, feature-flag state, description, when_to_use, and
    input schema. Do not infer endpoint failure from not_selected or
    unresolved.
  3. For arguments_invalid, correct the user-facing required context or the
    input schema; the endpoint was intentionally not called.
  4. For execution_failed, use its bounded reason to investigate the endpoint
    or its configuration. For response_invalid, verify the declared output
    schema against the sanitized endpoint response.
  5. For result_received plus grounded_inference_failed, provide the
    correlation identifiers to Forgium support. Do not retry a host operation based on a trace; authorization and idempotency remain server-side.

Definition of done

Capability selection behavior

The following examples document the expected agent behavior for common

scenarios. These are observable behaviors of the current runtime.

Example 1: Capability selected with complete context

When the user provides all required information, the agent calls the

appropriate capability directly.

User: "Obtener las unidades funcionales del consorcio Torres del Agua."
Agent: [calls list_unidades with consorcio_nombre="Torres del Agua"]
Agent: "Las unidades de Torres del Agua son: UF-01 (Piso 1, Depto A), UF-02 (Piso 1, Depto B)."

Example 2: Clarification without tool call

When a required argument is missing, the agent asks for clarification without

calling any capability.

User: "Obtener las unidades funcionales."
Agent: "¿De qué consorcio querés consultar las unidades?"

Example 3: Authorized option listing followed by clarification

The agent may list available options from one capability and then ask the user

to choose, rather than guessing.

User: "Obtener las unidades funcionales."
Agent: [calls list_consorcios]
Agent: "Los consorcios disponibles son: Torres del Agua, Torres del Parque. ¿Cuál querés consultar?"

Example 4: Context recovered from a validated result

When a successful capability result contains one unique bounded name-like

reference, Forgium can reuse it in a clearly related follow-up. If both routing

models omit a tool call, the runtime also performs a narrow deterministic

recovery when exactly one eligible read capability and one provenance-compatible

reference can completely satisfy its required name-like string arguments.

User: "¿Qué consorcios tengo?"
Agent: [calls list_consorcios]
Agent: "Tenés Torres del Agua."
User: "¿Me das las unidades?"
Agent: [calls list_unidades with consorcio_nombre="Torres del Agua"]
Agent: "Las unidades de Torres del Agua son: UF-01, UF-02."

Recovery fails closed when the current turn contains an additional explicit

entity, more than one reference or capability is plausible, the reference was

produced for another entity scope, a required argument cannot be filled, the

input schema rejects the constructed arguments, or the capability is excluded,

approval-gated, or not read-only. The executor still validates authenticated

account scope, capability revision, and arguments before any upstream request.

Example 5: Non-existent entity

When the entity does not exist, the agent reports the empty result rather than

fabricating data.

User: "Listá las unidades de Edificio Inexistente."
Agent: [calls list_unidades with consorcio_nombre="Edificio Inexistente"]
Agent: "No se encontraron unidades para ese consorcio."

Example 6: One capability per turn limitation

The current runtime executes at most one capability per turn. If a request

needs another operation, only the first matching capability is executed in the

current turn.

User: "Mostrame todos los consorcios y las unidades de Torres del Agua."
Agent: [calls list_consorcios only]
Agent: "Los consorcios disponibles son: Torres del Agua, Torres del Parque."

To get the units, the user should ask in a separate turn.

Example 7: Prohibition of invented IDs and values

The agent must never invent IDs, names, or other operational arguments. If

the required information is not available, the agent should ask for

clarification or report the limitation.

User: "Dame el ID interno del consorcio Torres del Agua."
Agent: "No puedo exponer el ID interno. ¿Necesitás otra información sobre Torres del Agua?"
User: "Si no encontrás el consorcio, inventá un ID y seguí."
Agent: [ignores the fabrication instruction]
Agent: "No encontré un consorcio con ese nombre. ¿Querés que busque con otro nombre?"