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:
- Read the capability's
descriptionandwhen_to_useas routing metadata. - Read
input_schemaand identify every field inrequired. - Distinguish optional properties from alternative required inputs. For
example,oneOfcan require exactly one selector such asitem_idoremail. - Use values from the user or a validated capability result. Never invent an
identifier, URL, price, status, or missing business value. - 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:
- use an exact public
https://URL on port443in the manifest; - accept and return
application/json; - validate input and enforce its business rules;
- return only fields approved for the chat; and
- have a stable request and response contract.
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:
- no resource matches the authenticated subject and request; or
- matching resources exist but none is currently eligible for the requested
operation, such as cancelling an already-cancelled expense, closing an
already-closed case, or paying an already-paid invoice.
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 checks | Required preparation response | Host responsibility |
|---|---|---|
| No matching resource | not_found | Confirm that no operation is available for the authenticated subject and request. |
Matching resource(s), but 0 eligible operations | not_found | Apply current-state preconditions; do not offer an invalid operation. |
1 | confirmation_required | Create one subject-scoped operation reference and preview. |
2–8 | selection_required | Return every candidate the user must distinguish, with unique option references and labels. |
More than 8 | selection_required with a host-defined bounded candidate set | Apply 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:
| Operation | Route |
|---|---|
| List own capabilities | GET /v1/capabilities |
| Create and publish one capability | POST /v1/capabilities |
| Validate and publish capabilities | POST /v1/capabilities/import |
| Get or publish a replacement revision | GET / PATCH /v1/capabilities/{capabilityId} |
| Contract test | POST /v1/capabilities/{capabilityId}/test |
| Enable or disable | POST /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
| Error | Cause | Resolution |
|---|---|---|
| CREDENTIAL_NOT_CONFIGURED | The referenced credential has not been provisioned. | Configure credential_ref through PUT /v1/capability-credentials/{credentialRef} before import, update, or enable. |
| HOST_INTERACTION_INVALID | The preparation endpoint returned a shape outside the closed interaction contract. | Return exactly one of the documented preparation statuses and respect all bounds. |
| CAPABILITY_DISABLED | The capability is disabled. | Re-enable it explicitly with POST /v1/capabilities/{capabilityId}/enable and the current If-Match revision. |
| REVISION_CONFLICT | If-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_5 | The 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.type | Required request fields |
|---|---|
bearer | auth_type: "bearer", secret |
api_key | auth_type: "api_key", header_name: "x-api-key" or "x-api-token", secret |
basic | auth_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.
| Status | Meaning | Was the capability endpoint called? |
|---|---|---|
not_selected | The selection inference returned no usable capability call. | No. |
unresolved | The selection inference emitted the bounded unresolved-request signal. Its reason is not authoritative. | No. |
selected | A candidate capability and its displayed revision were selected; input validation follows. | Not yet. |
arguments_invalid | The selected call failed the capability input schema before execution. | No. |
execution_failed | The executor could not complete a valid selected read. | It may have been contacted; inspect reason_code. |
empty_result | The sanitized result was structurally empty, for example null, [], or { "items": [] }. | Yes. |
result_received | The executor returned a non-empty result that passed its output contract. | Yes. |
response_generated | The turn reached an answer, approval, or safe fallback terminal path. | Depends on preceding events. |
runtime_failed | Workers 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:
| Code | Semantics |
|---|---|
no_tool_call | The selection response had no function call. It does not prove missing context, bad configuration, or an intent classification. |
no_active_capabilities | No active, eligible capability was loaded for the run. |
policy_out_of_scope | The selected tool was not in the authenticated account's eligible tool set. |
coverage_gap, out_of_scope, agent_dead_end | Bounded unresolved-request signals emitted by the selection model. They are operational hints, not authoritative intent or authorization decisions. |
schema_required_missing, schema_invalid | The 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_required | The executor rejected the request's current capability state or validated input. |
upstream_timeout, upstream_unavailable, upstream_http_error | The executor could not obtain a usable upstream response. |
response_invalid | The upstream response could not satisfy the capability response contract, including malformed, oversized, or schema-invalid content. |
structurally_empty | The executor returned a successful sanitized result with no structural values. It is not an execution error. |
grounded | A non-empty read result was followed by a completed grounded response, or an empty result was answered deterministically. |
answer_only | The turn finished without a capability call. |
host_interaction | A 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_fallback | The 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_failed | Workers 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_exceeded | The 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
- Save the
run_id,message_id, event sequence, model, and timing from the
same HTTP response. - 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 fromnot_selectedorunresolved. - For
arguments_invalid, correct the user-facing required context or the
input schema; the endpoint was intentionally not called. - For
execution_failed, use its bounded reason to investigate the endpoint
or its configuration. Forresponse_invalid, verify the declared output
schema against the sanitized endpoint response. - For
result_receivedplusgrounded_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
- [ ] Endpoint and local JSON data source implemented and directly tested.
- [ ] Endpoint exposed at an exact reachable HTTPS URL on port 443.
- [ ] Endpoint enforces its scoped token on every request.
- [ ] Any host-owned operation uses a read-only
host_interactionpreparation capability. - [ ] Preparation is implemented as a just-in-time host preflight; it does not rely on the model or
business_rulesto enforce resource state. - [ ] Preparation semantics are tested: no matching resource and matching-but-ineligible resources return
not_found; one eligible operation returnsconfirmation_required; multiple eligible operations returnselection_required. - [ ] Ambiguous or over-limit candidate sets never become
not_foundsolely because they require selection. - [ ] The mutation endpoint revalidates authorization, current resource state, revision/fingerprint, and idempotency after confirmation.
- [ ] Confirmation, selection, authorization, concurrency, idempotency, and mutation remain in the host application.
- [ ] Token is provisioned through the credential vault and never appears in source, manifest, logs, or output.
- [ ] Manifest contains the real URL,
credential_ref, bounded schemas, and no secrets. - [ ] Import validates and returns an active capability.
- [ ] Contract test passes for the imported revision.
- [ ] Any disable/enable operation uses the current
If-Matchrevision. - [ ] The chat query produces a response grounded in the endpoint result.
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?"