# Forgium Agent public integration documentation — full source pack Manifest version: 38 Contract version: 7.0.0 --- BEGIN docs/integration/capability-quickstart.md --- # Capability quickstart — Your first read capability This guide walks you through validating and publishing a read-only business capability end-to-end. By the end, your agent will call your endpoint to answer user questions with live data. **Time:** ~15 minutes **Prerequisites:** A Forgium API key and an HTTPS endpoint (or local development server) ## Overview ```txt Scaffold → Implement → Preflight → Credential → Import (active) → Test → Chat ``` ## Step 1: Generate the scaffold Use the CLI to generate a read-only capability scaffold: ```bash bun run capability:scaffold -- --domain people --name lookup_person --out ./my-capability ``` This creates: - `manifest.json` — Capability manifest with placeholder values - `endpoint.js` — Reference endpoint implementation - `test-local.js` — Local test script - `README.md` — Instructions and next steps ## Step 2: Implement your endpoint Edit `endpoint.js` and replace the placeholder logic with your actual business logic. The endpoint must: - Accept POST requests with JSON body - Validate input against your `input_schema` - Return only fields defined in `output_schema` - Enforce your own authorization and business rules Example implementation: ```javascript app.post("/v1/lookup_person", (req, res) => { const { query } = req.body; // Your business logic here const result = lookupPerson(query); // Return only fields defined in output_schema res.json({ id: result.id, result: result.name }); }); ``` ## Step 3: Test locally Run the local test to verify your endpoint works: ```bash cd ./my-capability node test-local.js ``` Expected output: ``` 🧪 Running tests on http://localhost:XXXXX 1. Testing health check... ✅ Health check passed 2. Testing capability with valid input... ✅ Capability response structure valid 3. Testing capability with invalid input... ✅ Invalid input correctly rejected ✅ All tests passed! ``` ## Step 4: Update the manifest Edit `manifest.json` and replace the placeholder values: 1. **URL**: Replace `https://your-endpoint.example.com/v1/lookup_person` with your real endpoint URL 2. **Schemas**: Update `input_schema` and `output_schema` to match your actual data structure 3. **Examples**: Add real user messages and tool inputs 4. **Business rules**: Update with your actual business rules ## Step 5: Validate with the linter Run the linter to check your manifest: ```bash bun run capability:lint -- ./my-capability/manifest.json ``` The linter checks for: - Placeholder values (must be replaced) - Invalid URLs - Secret-shaped values - Unsupported schema keywords - Missing schema bounds Expected output for a valid manifest: ``` ✓ Manifest is valid ``` If there are errors, fix them before proceeding. ## Step 6: Provision your endpoint credential Store your endpoint token in the credential vault. The token is encrypted before storage and never appears in logs or API responses: ```bash curl -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 '{"auth_type":"bearer","secret":"YOUR_ENDPOINT_TOKEN"}' ``` Expected response: ```json { "credential_ref": "cred_people_api", "status": "configured" } ``` ## Step 7: Import your manifest You can create a capability in two ways: **single** (recommended for adding one capability) or **import** (for bulk creation of multiple capabilities at once). ### Option A: Create a single capability ```bash curl -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: create-people-$(date +%s)" \ -d '{ "domain": "people", "capability": { "name": "lookup_person", "description": "Looks up a person by name or email.", "when_to_use": "Use when a user asks for person details.", "method": "POST", "url": "https://your-endpoint.example.com/v1/lookup_person", "auth": { "type": "bearer", "credential_ref": "cred_people_api" }, "safe_headers": { "accept": "application/json", "content-type": "application/json" }, "input_schema": { ... }, "output_schema": { ... }, "business_rules": ["Your backend enforces authorization."], "examples": [], "side_effect": "read", "requires_approval": false, "timeout_ms": 5000, "retry_count": 1 } }' ``` Expected response: ```json { "id": "cap_...", "lifecycle_status": "active", "revision": 1 } ``` ### Option B: Import a manifest (bulk) Import the manifest to validate and publish one or more active capabilities: ```bash curl -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/import" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: import-people-$(date +%s)" \ --data-binary @./my-capability/manifest.json ``` Expected response: ```json { "capabilities": [ { "id": "cap_...", "lifecycle_status": "active", "revision": 1 } ] } ``` Save the `id` — you'll need it for the next steps. ## Step 8: Run the contract test Test your capability with a sample input: ```bash curl -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":{"query":"test query"}}' ``` Expected response: ```json { "status": "passed", "capability_id": "cap_...", "revision": 1 } ``` If the test fails, check your endpoint logs and fix any issues. ## Step 9: Manage published state Import already publishes the validated capability. Disable it explicitly when the endpoint must stop receiving traffic: ```bash curl -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/$CAPABILITY_ID/disable" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "If-Match: 1" ``` Expected response: ```json { "id": "cap_...", "lifecycle_status": "disabled", "revision": 2 } ``` Re-enable it only through the explicit enable operation. State changes and revisions require the current `If-Match` value: ```bash curl -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/$CAPABILITY_ID/enable" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "If-Match: 2" ``` An update uses `PATCH` with `If-Match` as well. It validates the complete new manifest and publishes it atomically; it never creates a draft or silently re-enables a disabled capability. Capability IDs are immutable, and each successful update or state transition increments its revision. ## Step 10: Verify with a chat message Send a test message through the HTTP channel: ```bash curl -X POST "$FORGIUM_AGENT_BASE_URL/v1/channels/http/messages" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": { "type": "text", "body": "What is the email of Juan Pérez?" } }' ``` The agent should call your capability and return a response based on your endpoint's data. ## Preparing a host-owned operation Public capabilities are read-only. If the host needs to continue with a confirmation or selection, register a read-only preparation capability with `result_handling: "host_interaction"` and the closed output schema described in the [canonical host-interaction output schema](capability-handoff.md#canonical-host-interaction-output-schema). Forgium returns the bounded `interaction` result but does not execute the operation. The host application owns confirmation, authorization, idempotency, concurrency, and the eventual mutation. Treat the preparation endpoint as a just-in-time preflight. Forgium calls it after the user expresses an operation intent; the host does not need to predict that intent in advance. Resolve matching resources and check their current eligibility in deterministic host code. Return `not_found` when nothing matches or when matches exist but the requested operation is not currently available. If preparation succeeds, repeat authorization and state checks at mutation time because the resource may change before confirmation. ## Environment-specific configuration ### Local development For local development, you can use `localhost` URLs: ```json { "url": "http://localhost:3000/v1/lookup_person" } ``` Note: `localhost` URLs cannot be called by deployed Workers. Use them only for local testing. ### Staging For staging, use your staging endpoint URL: ```json { "url": "https://staging-api.example.com/v1/lookup_person" } ``` ### Production For production, use your production endpoint URL: ```json { "url": "https://api.example.com/v1/lookup_person" } ``` ## Schema requirements All schemas must follow these rules: - **Objects**: Must have `additionalProperties: false` - **Strings**: Must have `maxLength` - **Arrays**: Must have `maxItems` - **`input_schema` keywords**: `type`, `properties`, `required`, `additionalProperties`, `items`, `maxItems`, `minLength`, `maxLength`, `enum`, `const`, `description`, `anyOf`, `oneOf`, `not`, `pattern` - **`output_schema` keywords**: `type`, `properties`, `required`, `additionalProperties`, `items`, `maxItems`, `minLength`, `maxLength`, `enum`, `const`, `description` `description` is an annotation. Every other keyword is enforced by the runtime. All keywords not listed for the relevant schema are rejected at import, including `allOf`, `$ref`, `minimum`, `maximum`, `minItems`, and `format`. Composition keywords are intentionally input-only: output schemas must retain one explicit shape so Forgium can safely remove fields not declared in the schema. ### Constrain a bounded string format `pattern` is supported only in `input_schema` string fields that declare `maxLength`. It uses ECMAScript regular-expression syntax and is enforced before the capability endpoint is called. For example, an optional `periodo` argument in `YYYY-MM` format can reject invalid months: ```json { "type": "object", "additionalProperties": false, "properties": { "periodo": { "type": "string", "maxLength": 7, "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", "description": "Optional accounting period in YYYY-MM format." } } } ``` `pattern` is not supported in `output_schema`. It improves argument quality but does not replace endpoint validation, authorization, or business rules; the capability backend remains authoritative. ### Require exactly one selector (XOR) Use `oneOf` when a capability needs exactly one of two alternative arguments. The following `input_schema` accepts either `unidad_numero` or `persona_nombre`, but rejects a request that provides neither or both: ```json { "type": "object", "additionalProperties": false, "properties": { "unidad_numero": { "type": "string", "maxLength": 32 }, "persona_nombre": { "type": "string", "maxLength": 200 } }, "oneOf": [ { "required": ["unidad_numero"] }, { "required": ["persona_nombre"] } ] } ``` `anyOf` is also supported for inclusive alternatives. To express the same XOR without `oneOf`, combine it with `not`: ```json { "type": "object", "additionalProperties": false, "properties": { "unidad_numero": { "type": "string", "maxLength": 32 }, "persona_nombre": { "type": "string", "maxLength": 200 } }, "anyOf": [ { "required": ["unidad_numero"] }, { "required": ["persona_nombre"] } ], "not": { "required": ["unidad_numero", "persona_nombre"] } } ``` Each `anyOf` or `oneOf` array must contain between one and eight bounded schemas. ## Security checklist - [ ] Never commit tokens, API keys, or secrets - [ ] Use environment variables or secret managers for credentials - [ ] The manifest should never contain sensitive values - [ ] Validate all input at your endpoint - [ ] Return only fields safe for model context - [ ] Enforce your own authorization and business rules ## Troubleshooting ### Linter rejects my manifest Run the linter with `--format json` to see detailed diagnostics: ```bash bun run capability:lint -- ./my-capability/manifest.json --format json ``` ### Contract test fails Check your endpoint logs for errors. Common issues: - Invalid input validation - Missing required fields in response - Response doesn't match `output_schema` ### Capability not called by agent Verify: - Capability is `active` (not `disabled`) - Credential is `configured` - Endpoint URL is accessible from Forgium Workers - Input schema matches the agent's expected arguments ## Next steps - [Self-service business capabilities](capability-handoff.md) — Full lifecycle reference - [Forgium-Context verification](auth.md) — Validate signed requests at your endpoint - [Agent policy](agent-policy.md) — Configure response instructions and topic scope - [Error reference](errors.md) — Common error codes and fixes --- END docs/integration/capability-quickstart.md --- --- BEGIN docs/integration/use-with-an-agent.md --- # Use Forgium Agent with an agent This is the single public entrypoint for a coding agent integrating a host application with Forgium Agent. The public documentation is the canonical contract. The optional Skill is not currently distributed as an installable package; use this protocol unless your runtime already supports a verified local `SKILL.md`. ## Machine-readable discovery An agent can discover the complete public contract without guessing URLs: - [Agent manifest](/agent-manifest.json) — canonical resources, versions, routes, required environment variables, and stop conditions. - [Agent index](/agent-index.json) — generated resource index and hashes. - [LLM index](/llms.txt) — concise reading order and route summary. - [Raw OpenAPI contract](api.openapi.yaml) — exact public request and response schemas. - [Contract retrieval](contract-retrieval.md) — direct download, validation, and blocker reporting when an agent cannot locate the contract. - [HTTP integration reference](reference-implementation.md) — server-side adapter pattern and production checklist. The same resources are available from the public site at: ```text https://docs.forgium.dev/agent-manifest.json https://docs.forgium.dev/agent-index.json https://docs.forgium.dev/llms.txt https://docs.forgium.dev/docs/integration/api.openapi.yaml ``` Use only links published by the manifest or this page. Do not guess or enumerate undocumented `openapi.json`, `swagger.json`, or API paths. ## Decision protocol 1. Read the [agent manifest](/agent-manifest.json), this page, the [Overview](overview.md), and [Environments](environments.md). 2. Determine the goal: - **Connectivity test only:** do not inspect or modify an unrelated repository; use [Getting started](getting-started.md) to send the first HTTP message. - **Code integration:** identify the host application's repository and where the integration belongs. Before running repository commands, confirm the working directory with `pwd` and change to the host repository explicitly. Do not assume a data source, tunnel, framework, or deployment model. 3. For a code integration, choose the smallest applicable route: - **HTTP:** the host application needs a synchronous agent response. - **Capabilities:** the agent needs live data or a host-owned operation preparation result. Capabilities may coexist with HTTP, but are not required for a basic message integration. 4. When a real API call is needed, confirm the target environment's `FORGIUM_AGENT_BASE_URL` and `FORGIUM_AGENT_API_KEY`. If either is missing, [request access](request-access.md); continue only with local work or mocks, and do not claim an end-to-end result. 5. Read the [OpenAPI contract](api.openapi.yaml) and, if discovery fails, follow [Contract retrieval](contract-retrieval.md) before writing calls. For an HTTP code integration, use the [HTTP integration reference](reference-implementation.md) as a pattern only; implement only what the repository needs. 6. Run the applicable local or authenticated smoke test. For a code integration, also run the host repository's tests. For capabilities, follow the complete lifecycle in [Capability quickstart](capability-quickstart.md) or the detailed [Self-service business capabilities](capability-handoff.md) reference. 7. Report what changed, the evidence that passed, and every concrete blocker with its next action. Do not collapse multiple blockers into an artificial single blocker. ## Stop conditions Stop the integration and report the blocker instead of guessing or probing when: - the canonical OpenAPI contract or selected route guide cannot be reached through the published links; use [Contract retrieval](contract-retrieval.md) once, then report the exact HTTP status and content type instead of probing; - `FORGIUM_AGENT_BASE_URL` or `FORGIUM_AGENT_API_KEY` is missing for a real API call; - the access bundle does not identify the requested environment; - the endpoint URL, credential, or capability manifest is a placeholder, unauthenticated, or belongs to another environment; or - a contract test, explicit enable/disable transition, or final chat verification fails. A missing Git repository is not by itself an integration failure. Only run Git commands when the host repository uses Git or when version-control evidence was specifically requested. ## Route reference | Goal | Read first | Completion evidence | |---|---|---| | Connectivity | [HTTP quickstart](getting-started.md) | First HTTP message returns an assistant response | | HTTP code integration | [Overview](overview.md), [HTTP reference](reference-implementation.md), and [OpenAPI](api.openapi.yaml) | Host tests plus authenticated smoke test | | Host data or operation preparation | [Capabilities lifecycle](capability-handoff.md) | Validated active import, contract test, and chat verification | ## Copy this prompt ```text You are integrating my application with Forgium Agent. Use only the official public integration documentation at https://docs.forgium.dev/docs. Start with these canonical resources; do not guess or enumerate undocumented URLs: 1. https://docs.forgium.dev/agent-manifest.json 2. https://docs.forgium.dev/docs/integration/use-with-an-agent 3. https://docs.forgium.dev/docs/integration/overview 4. https://docs.forgium.dev/docs/integration/environments 5. https://docs.forgium.dev/docs/integration/api.openapi.yaml Use the manifest's published links for the selected route: - HTTP quickstart: https://docs.forgium.dev/docs/integration/getting-started - Capabilities lifecycle: https://docs.forgium.dev/docs/integration/capability-handoff - Request access: https://docs.forgium.dev/docs/integration/request-access - Contract retrieval: https://docs.forgium.dev/docs/integration/contract-retrieval - HTTP integration reference: https://docs.forgium.dev/docs/integration/reference-implementation If you are only testing connectivity, do not inspect or modify an unrelated repository: use the access bundle and the HTTP quickstart to send one message. Do not run repository or git discovery commands for a connectivity-only test. If you are implementing code, first confirm the working directory with `pwd` and explicitly inspect the host application's repository. Choose the minimum integration it actually needs: - HTTP when it needs a synchronous agent response; or - capabilities when the agent needs approved live data or actions from the host. Capabilities are optional and may coexist with HTTP. When a real API call or deployment is required, confirm these values for the selected environment before using them: - FORGIUM_AGENT_BASE_URL - FORGIUM_AGENT_API_KEY If either is missing, ask the Forgium administrator for the environment-specific access bundle and continue only with local implementation or mocks. Read the canonical OpenAPI contract before generating requests. If the agent cannot locate it through the published links, follow the Contract retrieval commands once and report the exact result. Do not substitute another contract. Implement only the applicable route, run tests, and record evidence. For capabilities, use the public lifecycle: protect and expose the host endpoint, provision its scoped credential through the Public API vault, create a secret-free manifest, import to publish an active revision, contract-test, and verify through chat. Use explicit `enable` or `disable` transitions with the current `If-Match` revision. Repeat separately per environment. Never use a placeholder URL or an unauthenticated endpoint. If a published page or the canonical contract is unavailable, stop and report that blocker. Do not probe guessed OpenAPI, Swagger, API, or internal URLs. Report every blocker separately with its next action. Report changes, verification evidence, and every actionable blocker. Do not claim a capability is active unless import validation and final chat verification pass. Never request Admin API access or internal secrets. ``` ## Current runtime behavior The following constraints apply to the current version of Forgium Agent: - **One capability per turn.** The agent executes at most one capability call per turn. A second operation requires another turn or one capability designed for the complete intent. - **Clarification over fabrication.** When a required argument is missing, the agent asks for clarification rather than inventing values. - **Safe history reuse.** The agent reuses unambiguous context from conversation history but asks for confirmation when the history is ambiguous. - **Validated result context.** After a successful capability returns one unique, bounded name-like reference, Forgium may reuse it for a clearly related follow-up in the same conversation. Explicit conflicting context is not carried forward; IDs, credentials, amounts, dates, and raw results are not stored as conversational references, and the executor revalidates every argument. - **No internal ID exposure.** The agent does not expose internal identifiers (database IDs, revision numbers) unless the capability explicitly returns them. - **Grounded responses.** The agent bases its responses on the capability result and must not invent data when the result is empty or the capability fails. - **Host-owned operations.** Contract 5 capabilities are read-only. A host may expose a `result_handling: "host_interaction"` preparation capability; Forgium returns a bounded `interaction` union and never executes or authorizes the operation. - **Just-in-time host preflight.** Forgium invokes the preparation capability after the user expresses an operation intent. The host resolves matching resources and checks current eligibility then; it does not need to predict the intent in advance. `not_found` covers both no match and matching-but-ineligible resources. - **Opaque references.** Route by `interaction.owner` and `interaction.type`. Keep `operation_ref`, `selection_ref`, and `option_ref` in the host flow; they are not sent to Workers AI, written to conversation history or metadata, persisted by Forgium, or resent in a later interaction. - **Revalidate before mutation.** Preparation is not a lock or authorization grant. After confirmation, the host must recheck authorization, state, concurrency/revision, and idempotency before mutating. - **Terminal reporting.** The host performs selection, confirmation, authorization, concurrency, idempotency, and mutation. It may report only the terminal status through `POST /v1/conversations/{conversationId}/operation-results`. For detailed behavior examples, see [Self-service business capabilities](capability-handoff.md#host-managed-interactions). ## Expected result A successful run leaves a tested first HTTP message and, when needed, a read-only capability or host-interaction preparation capability active and verified against the host endpoint. Continue with: - [Overview](overview.md) - [Getting started](getting-started.md) for the manual HTTP quickstart - [Environments](environments.md) - [Request access](request-access.md) - [Contract retrieval](contract-retrieval.md) - [HTTP integration reference](reference-implementation.md) - [Self-service business capabilities](capability-handoff.md) - [Public API contract](api.openapi.yaml) --- END docs/integration/use-with-an-agent.md --- --- BEGIN docs/integration/overview.md --- # Forgium Agent — Public API overview Forgium Agent is a conversational-agent service. Start with [Use Forgium Agent with an agent](use-with-an-agent.md) when a coding agent is doing the integration. Your application can send a user message to the Public API and receive an agent response. For automated discovery, use the [agent manifest](/agent-manifest.json). It publishes the canonical guides, raw OpenAPI contract, environment variables, and stop conditions. The manifest is the source for the generated [agent index](/agent-index.json) and [LLM index](/llms.txt). ## What you can build Choose the path that matches the host application: | Need | Use | |---|---| | A web, mobile, or backend application needs a synchronous agent reply | `POST /v1/channels/http/messages` | | The agent needs live data or a host-owned operation preparation result | [Capability quickstart](capability-quickstart.md) | | The account needs shared response instructions or topic scope | [Agent policy](agent-policy.md) | | The account needs to find unresolved requests and capability gaps | [Capability-gap diagnostics](diagnostics.md) | The HTTP channel accepts text-only JSON messages and one optional binary image through `multipart/form-data`. Use the `payload` part for the JSON request and the `image` part for a single JPEG, PNG, or WebP file up to 4 MiB; public base64 image fields are not supported. ## Access boundary Before integrating, ask the Forgium administrator for this access bundle for the target environment: ```bash FORGIUM_AGENT_BASE_URL=https:// FORGIUM_AGENT_API_KEY=mg_xxx ``` Use the key only with the Public API (`/v1/*`). If it is missing or rejected, ask the administrator for help. ## Recommended reading order For an agent: 1. [Use Forgium Agent with an agent](use-with-an-agent.md) — decision protocol. 2. [Environments](environments.md) — keep environments isolated. 3. [Request access](request-access.md) — obtain and validate the bundle when needed. 4. [Contract retrieval](contract-retrieval.md) — download and validate the raw contract if an agent cannot discover it through navigation. 5. [Authentication](auth.md) — API-key rules. 6. Read [Getting started](getting-started.md) for the HTTP quickstart, or [Self-service business capabilities](capability-handoff.md) when the host application must provide live data or prepare a host-owned operation. 7. Read [Configure an agent policy](agent-policy.md) when shared response instructions or best-effort topic scope are required. 8. Read [Capability-gap diagnostics](diagnostics.md) when the account wants to identify unsupported requests and prioritize new capabilities. 9. Use the [OpenAPI contract](api.openapi.yaml) for exact request and response fields. For a server-side HTTP adapter, see [HTTP integration reference](reference-implementation.md). For a manual integration, start at [Getting started](getting-started.md). --- END docs/integration/overview.md --- --- BEGIN docs/integration/environments.md --- # Environments Keep Forgium Agent access, host-application endpoints, and credentials isolated by environment. A capability registered in one environment is not a deployment mechanism for another. ## Access bundles Request a separate bundle from the platform operator for every environment you will use: ```bash # Example only; use values issued by the Forgium administrator. FORGIUM_AGENT_BASE_URL=https:// FORGIUM_AGENT_API_KEY=mg_ ``` Never point a production host application at a development or staging API key. Never reuse a capability endpoint token across environments unless its owning application explicitly authorizes that use. | Environment | Intended use | Capability endpoint | |---|---|---| | Development | Local implementation and contract preparation | Local endpoint for direct tests; an authorized HTTPS tunnel is required before a deployed Worker can call it. | | Staging | End-to-end verification before release | Staging HTTPS endpoint, staging credential reference, and staging Forgium Agent access bundle. | | Production | Customer traffic | Production HTTPS endpoint, production credential reference, and production Forgium Agent access bundle. | ## Promotion workflow Repeat the capability lifecycle in each target environment; do not copy a staging capability ID or credential reference into production. 1. Deploy the host application's endpoint to the target environment and verify its scoped-token authentication directly. 2. Use the target environment's `FORGIUM_AGENT_*` bundle to store that endpoint token with `PUT /v1/capability-credentials/{credentialRef}`. 3. Import the target environment's manifest with its exact HTTPS URL. 4. Contract-test the imported active capability at its current revision. 5. Send a real chat request to verify that the agent used the target endpoint. See [business capabilities](capability-handoff.md) for the manifest and API commands. ## Release safety Before enabling production traffic, verify all of the following: - the base URL and API key belong to production; - the manifest contains the production endpoint URL and no secrets; - the endpoint accepts only its production scoped token; - the contract test passed for the current capability revision; and - a chat smoke test returns a result grounded in the production endpoint. ## Staging E2E gate The repository includes two independent generic HTTPS reference consumers and a release-gate command: ```bash FORGIUM_E2E_ENV=staging bun run test:e2e:staging ``` The command requires the explicit staging URL, API key, control token, two consumer URLs/tokens/control tokens, and a second access-bundle key for the isolation assertion. It rejects non-HTTPS URLs, configured production hosts, missing inputs, and guessed defaults. It emits only a redacted report. The consumer setup, reset, and token-rotation endpoints exist only in staging and accept synthetic data. ## Rollback If a newly enabled capability behaves incorrectly, disable it with its current revision: ```bash curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/capabilities/$CAPABILITY_ID/disable" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "If-Match: $FORGIUM_CAPABILITY_REVISION" ``` Then correct the host endpoint or manifest, revise the capability, run its contract test, and enable it explicitly with the resulting current revision. If the API key or endpoint token may have leaked, notify the Forgium administrator and rotate the affected secret before resuming traffic. --- END docs/integration/environments.md --- --- BEGIN docs/integration/request-access.md --- # Request access The Public API access bundle is issued out of band by the Forgium administrator. The operator must configure the approved recipient and channel for each environment; this public contract does not assume a specific ticketing system, chat room, or email address. ## What to request Send the following template through the operator-approved channel: ```text Subject: Forgium Agent Public API access request Application: Owner: Environment: Purpose: Callback or host endpoint: Please provide the environment-specific access bundle through the approved secret-sharing channel. I need: - FORGIUM_AGENT_BASE_URL - FORGIUM_AGENT_API_KEY Do not include the API key in this ticket, repository, prompt, or log. ``` The operator may require additional application or deployment details before issuing access. Do not invent a URL, key, credential reference, or environment. ## Validate the bundle After receiving the values, keep them in the host application's secret manager and validate the environment without echoing the key: ```bash export FORGIUM_AGENT_BASE_URL="" export FORGIUM_AGENT_API_KEY="" curl -fsS "$FORGIUM_AGENT_BASE_URL/health" curl -fsS "$FORGIUM_AGENT_BASE_URL/v1/capabilities" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` The first response must identify the Forgium Agent API. The authenticated request must succeed for the intended environment. A `401` means the key is missing, invalid, expired, revoked, or paired with the wrong host; ask the administrator to reissue the matching bundle rather than trying another environment's key. For a capability, also verify that the endpoint URL and endpoint credential belong to the same environment before importing the manifest. See [Environments](environments.md) and [Self-service business capabilities](capability-handoff.md). ## Security boundary - Never commit an API key or host-endpoint token. - Never place secrets in `forgium-agent.capabilities.json`, prompts, screenshots, persisted agent context, or logs. - Request only the Public API bundle. Admin API tokens, platform secrets, and internal bindings are outside the integration contract. - If a secret may have been exposed, stop and ask the administrator to rotate it before continuing. --- END docs/integration/request-access.md --- --- BEGIN docs/integration/getting-started.md --- # Getting started This path uses the HTTP channel to verify that your application can send a text message and receive a synchronous agent response. ## 1. Receive the access bundle [Request the access bundle](request-access.md) from the Forgium administrator for the target environment. It contains: ```bash FORGIUM_AGENT_BASE_URL=https:// FORGIUM_AGENT_API_KEY=mg_xxx ``` Store the key in the host application's secret manager; do not commit it, include it in a capability manifest, or put it in logs. ## 2. Configure the integration If either value is unavailable, stop here and use [Request access](request-access.md). Do not substitute a guessed host, placeholder URL, or another environment's key. ```bash export FORGIUM_AGENT_BASE_URL="https://" export FORGIUM_AGENT_API_KEY="mg_xxx" ``` Confirm the public API is reachable: ```bash curl -fsS "$FORGIUM_AGENT_BASE_URL/health" ``` Expected response: ```json {"status":"ok","service":"agent-api"} ``` ## 3. Send the first message ```bash curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/channels/http/messages" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "integration-smoke-test", "message": { "type": "text", "body": "Hello, can you help me?" }, "metadata": { "source": "integration-smoke-test" } }' ``` A successful response contains a `conversation_id` and an assistant `message`. This is the completion evidence for a connectivity-only test. Persist the `conversation_id` in the host application and send it in later requests to continue the same conversation. `user_id` is an optional opaque identifier and is strongly recommended so Forgium can attribute AI usage to the individual user within the account. It is not mandatory during the current client migration and there is no announced cutoff date. If omitted, usage is still recorded for the account but not for an individual user within the account. ## 4. Send a message with an image Images use binary multipart transport. The `payload` part is JSON and the `image` part is one JPEG, PNG, or WebP file no larger than 4 MiB: ```bash curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/channels/http/messages" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -F 'payload={"user_id":"integration-image-test","message":{"type":"text","body":"Registrá este comprobante"}};type=application/json' \ -F 'image=@./receipt.png;type=image/png' ``` Forgium processes the image in memory and does not store the image, OCR or Markdown extraction, model reasoning, or confidence. If the operation needs more fields, answer the clarification in the same conversation; the image does not need to be uploaded again. Send `cancelar` to discard a pending operation without invoking the host application. Uploading an image alone does not define which business fields to extract. To receive structured values such as an invoice number, supplier, total, or transfer reference, the account must have an active capability with an `image_interpretation` declaration. See [Image-assisted capabilities](capability-handoff.md#image-assisted-capabilities) for the manifest shape; the host capability endpoint receives the validated arguments, while the chat response remains the user-facing result. ### What the chat receives after image processing Image interpretation does not add a raw OCR or extracted-fields object to the public response. The host receives the validated capability arguments through its capability endpoint, and the chat receives the normal `HttpChannelResponse` envelope: ```json { "conversation_id": "conv_xxx", "message": { "id": "msg_xxx", "role": "assistant", "type": "text", "body": "..." } } ``` The response behavior is bounded by the outcome: - **Successful read:** the assistant responds using the validated result from the host capability; it does not expose OCR text or model reasoning. - **Missing required fields:** the assistant asks for the missing values and keeps a short-lived pending draft. The next message continues the same operation. - **Ambiguous operation:** the assistant asks what the user wants to do and does not invoke a capability. - **Unreadable image or OCR failure:** the assistant returns a safe failure message and does not invoke the host. - **Host-owned preparation:** the response includes the documented `interaction` union for confirmation, selection, or not-found; the host owns the next action. The exact natural-language body can vary by capability result and locale. Hosts must consume the documented response envelope and `interaction` discriminator, not match one fixed assistant sentence. For an image-assisted mutation workflow, this response is a preparation step: Forgium returns the host-owned confirmation preview, but the host application must ask the user for confirmation and execute the mutation itself. Forgium never executes the final mutation. ```json { "conversation_id": "conv_xxx", "message": { "id": "msg_xxx", "role": "assistant", "type": "text", "body": "..." } } ``` ## 5. Select the next integration task - Register a [business capability](capability-handoff.md) when the agent needs read-only data or a host-owned operation preparation result. - Read [authentication](auth.md) before handling `401` responses or storing the key in deployment configuration. - Read [environments](environments.md) before promoting an integration beyond development. ## Troubleshooting | Response | Meaning | Next action | |---|---|---| | `401` | The API key is missing, invalid, expired, revoked, or unavailable for this environment. | Ask the Forgium administrator for a replacement. | | `422` | The request body is invalid. | Send a `message` with `type: "text"` and a non-empty `body`. | | `500` | The agent runtime could not complete the request. | Retry only if the host application's retry policy allows it; retain the response details and contact the operator if it persists. | The [OpenAPI contract](api.openapi.yaml) is authoritative for all request and response fields. If an agent cannot locate the contract, use the exact commands in [Contract retrieval](contract-retrieval.md); do not substitute another contract. For staging or production, validate the bundle and environment isolation described in [Environments](environments.md). For a server-side adapter pattern, error mapping, conversation handling, and production security checklist, see [HTTP integration reference](reference-implementation.md). --- END docs/integration/getting-started.md --- --- BEGIN docs/integration/reference-implementation.md --- # HTTP integration reference implementation This guide describes a minimal server-side adapter for an application that needs a synchronous Forgium Agent response. It is a development pattern, not a required framework or deployment model. ## Recommended boundary Keep the Forgium API key and upstream URL behind the host application's backend: ```text Client or UI │ │ POST /api/agent/messages ▼ Host application adapter │ validates input, authenticates the caller, │ applies timeout/rate limit, maps errors │ │ POST /v1/channels/http/messages ▼ Forgium Agent Public API ``` Do not call Forgium directly from a browser or mobile client when doing so would expose `FORGIUM_AGENT_API_KEY`. ## Example adapter contract The host application may expose a route such as `POST /api/agent/messages`. The exact route and framework belong to the host application. A small JSON contract is sufficient: ```json { "message": "Hello, can you help me?", "conversationId": "conv_optional" } ``` The adapter maps that request to the public HTTP channel contract: ```json { "user_id": "host-user-id", "conversation_id": "conv_optional", "message": { "type": "text", "body": "Hello, can you help me?" } } ``` The upstream response uses `conversation_id`; the host adapter may expose the same value as `conversationId` to match its own naming conventions. Preserve that identifier for subsequent messages in the same conversation. If the response includes `interaction`, route by its host-owned type and keep opaque references in the host flow. The host backend performs the operation and may report its terminal status through the operation-results callback. Never send the Forgium API key to the UI. For an image message, send the upstream request as binary multipart rather than embedding an attachment in JSON: ```bash curl -fsS -X POST "$FORGIUM_AGENT_BASE_URL/v1/channels/http/messages" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -F 'payload={"user_id":"host-user-id","conversation_id":"conv_optional","message":{"type":"text","body":"Registrá este comprobante"}};type=application/json' \ -F 'image=@./receipt.png;type=image/png' ``` The adapter should enforce the one-image, 4 MiB, JPEG/PNG/WebP limits before forwarding when it already has a request-size boundary. The binary image is processed in memory by Forgium and is not persisted. The public [OpenAPI contract](api.openapi.yaml) is authoritative for the upstream request and response shape. Do not generate a different upstream payload from this example. ## Configuration Read these values only on the server: ```bash FORGIUM_AGENT_BASE_URL= FORGIUM_AGENT_API_KEY= ``` Fail clearly when configuration is absent. A local adapter can return a configuration error without making an upstream request; this is useful for local smoke tests. Never include the key in a response, source file, client bundle, capability manifest, log, or persisted agent context. ## Error and resilience behavior The adapter should make failures actionable without leaking upstream secrets: | Situation | Recommended behavior | |---|---| | Missing configuration | Return a stable configuration error and HTTP `503`; do not call Forgium. | | Invalid caller input | Return a validation error and HTTP `4xx`; do not call Forgium. | | Forgium rejects authentication | Return a dependency/authentication error, redact the key, and request a matching environment bundle. | | Forgium times out or is unavailable | Apply the host retry policy, then return a dependency error with a correlation ID. | | Unexpected upstream response | Validate the response before returning it and log only safe diagnostics. | Choose status codes that match the host application's existing API conventions. Do not retry validation, authentication, or non-idempotent requests blindly. Follow the [idempotency guide](idempotency.md) when adding retries or replay protection. ## Production checklist Before exposing the adapter to customer traffic: - [ ] Authenticate the host application's caller. - [ ] Apply rate limiting and an appropriate request body limit. - [ ] Keep `FORGIUM_AGENT_API_KEY` server-side and store it in a secret manager. - [ ] Configure an explicit timeout and bounded retry policy. - [ ] Redact API keys and message contents from logs unless the product has an approved data-retention policy. - [ ] Validate both incoming input and the external response. - [ ] Preserve `conversation_id` only according to the host application's privacy and retention requirements. - [ ] Treat `interaction` as a host-owned preparation result, not a Forgium command. - [ ] Add tests for missing configuration, invalid input, upstream `401`, timeout, malformed response, and successful conversation continuation. - [ ] Run the authenticated smoke test against the intended environment. The host application's authentication and rate limiting are not supplied by Forgium. They remain required even when the Forgium API key is valid. ## Definition of done A minimal HTTP integration is ready for review when: 1. local tests pass with mocked upstream responses; 2. `/health` or the host equivalent reports dependency configuration without revealing secrets; 3. the adapter sends the exact payload described by the public contract; 4. the response is validated and the conversation identifier is preserved; and 5. an authenticated smoke test passes for the target environment, or the access bundle is documented as the remaining blocker. See [Getting started](getting-started.md) for the first authenticated message and [Contract retrieval](contract-retrieval.md) when an agent cannot locate the canonical OpenAPI file. --- END docs/integration/reference-implementation.md --- --- BEGIN docs/integration/contract-retrieval.md --- # Retrieve and validate the public contract Use this guide when a coding agent cannot find the OpenAPI or API reference through the documentation navigation. The contract is public and does not require an API key. ## Canonical resources Use the published machine-readable manifest first: ```text https://docs.forgium.dev/agent-manifest.json ``` The canonical raw OpenAPI file is: ```text https://docs.forgium.dev/docs/integration/api.openapi.yaml ``` The same link is published from the [agent entrypoint](use-with-an-agent.md), the [LLM index](/llms.txt), and the [agent index](/agent-index.json). Do not infer an API host from the OpenAPI document. The API host comes from the environment-specific `FORGIUM_AGENT_BASE_URL` access bundle. ## Direct retrieval A shell-based agent can retrieve the file without a search engine or URL enumeration: ```bash CONTRACT_URL="https://docs.forgium.dev/docs/integration/api.openapi.yaml" curl -fsSL \ -H 'Accept: application/yaml,text/plain' \ "$CONTRACT_URL" \ -o /tmp/forgium-agent.openapi.yaml ``` Confirm that the response is the raw contract rather than an HTML error page: ```bash test -s /tmp/forgium-agent.openapi.yaml grep -q '^openapi:' /tmp/forgium-agent.openapi.yaml head -20 /tmp/forgium-agent.openapi.yaml ``` When the local toolchain has Redocly available, validate the syntax and references: ```bash bunx @redocly/cli lint /tmp/forgium-agent.openapi.yaml ``` Before releasing a changed contract, run the release validation from the Forgium repository. It compares the current contract with its base branch and requires the changelog to change with it: ```bash bun run docs:validate:release ``` Set `FORGIUM_DOCS_BASE_REF` only when the release base is not `origin/main`. The expected contract version is published in `agent-manifest.json` and in the OpenAPI `info.version` field. Do not silently continue with a different version. ## If retrieval fails Stop and report the concrete failure when any of these conditions occurs: - the manifest or raw contract returns a non-success HTTP status; - the response is HTML, empty, or does not contain an `openapi:` declaration; - the downloaded contract fails validation; or - the contract version does not match the published manifest. Report the URL, HTTP status, content type, and the next action. Do not try undocumented `openapi.json`, `swagger.json`, API, or internal URLs. Do not substitute a contract found through an unrelated search result. Example blocker report: ```text Blocker: https://docs.forgium.dev/docs/integration/api.openapi.yaml returned HTTP 404 with Content-Type text/html. Next action: deploy the public documentation artifact containing the canonical OpenAPI file, then rerun the retrieval and lint commands. ``` Missing API credentials is a separate blocker from a missing documentation contract. Report both when both are present. ## Contract evidence for an integration Record these facts in the implementation handoff: - canonical contract URL; - HTTP status and content type; - OpenAPI version and `info.version`; - lint result or the reason lint could not run; and - whether the authenticated API smoke test was run. A local adapter may be implemented with mocks while credentials are pending, but it must be labelled locally verified rather than end-to-end verified. --- END docs/integration/contract-retrieval.md --- --- BEGIN docs/integration/capability-handoff.md --- # Self-service business capabilities > **New to capabilities?** Start with the [Capability quickstart](capability-quickstart.md) > 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. ```json { "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: ```text 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: ```json { "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 ```txt 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: ```txt 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: ```env 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 port `443` in 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: ```http POST https://consumer.example/v1/people/lookup HTTP/1.1 Authorization: Bearer Accept: application/json Content-Type: application/json Forgium-Context: {"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: ```http POST https://consumer.example/v1/host-interactions/prepare HTTP/1.1 Authorization: Bearer Accept: application/json Content-Type: application/json Forgium-Context: {"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: ```http POST https://api.example.com/v1/people/lookup Content-Type: application/json {"name":"Juan Pérez"} ``` ```json { "person_id": "person_123", "name": "Juan Pérez", "email": "juan@example.com" } ``` ## Create `/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. ```json { "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](capability-quickstart.md#schema-requirements) 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) ```bash 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) ```bash 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: ```text 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. ```json { "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 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 - [ ] 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_interaction` preparation capability. - [ ] Preparation is implemented as a just-in-time host preflight; it does not rely on the model or `business_rules` to enforce resource state. - [ ] Preparation semantics are tested: no matching resource and matching-but-ineligible resources return `not_found`; one eligible operation returns `confirmation_required`; multiple eligible operations return `selection_required`. - [ ] Ambiguous or over-limit candidate sets never become `not_found` solely 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-Match` revision. - [ ] 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. ```text 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. ```text 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. ```text 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. ```text 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. ```text 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. ```text 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. ```text 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?" ``` ```text 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?" ``` --- END docs/integration/capability-handoff.md --- --- BEGIN docs/integration/agent-policy.md --- # Configure an agent policy An agent policy is an optional account-wide configuration that defines **how the agent should behave**, not how users should express themselves. It is managed with the Public API key and applies to new runs on every channel, including the next message in an existing conversation. The policy regulates agent behavior across these dimensions: - **Language**: Response language, tone, and formality level. - **Topic scope**: Which subjects the agent can address. - **Natural interpretation**: How to understand user intent regardless of phrasing, typos, or colloquialisms. - **Ambiguity handling**: When to ask for clarification vs. proceed with reasonable assumptions. - **No invention**: Never fabricate data, names, amounts, or capabilities that don't exist. - **Privacy**: What information to protect and how to handle sensitive data. > **Important**: The policy should describe agent behavior, not restrict > how users express their requests. Users will phrase things naturally, > with typos, abbreviations, and colloquialisms. The agent must interpret > these flexibly. The API key is the only source of account ownership. Do not put an account ID in the URL or manifest. ## Policy structure `PUT /v1/agent-policy` creates or fully replaces the one active policy. ### Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `version` | `1` | Yes | Schema version (must be `1`). | | `instructions` | string | No | Free-text behavioral instructions for the agent. | | `allowed_topics` | array | No | Topic allowlist with `name` and `description`. | | `out_of_scope_response` | string | No | Custom fallback when request is outside allowed topics. | At least one of `instructions` or `allowed_topics` must be present. Topic names are compared case-insensitively after trimming. Unknown fields, empty values, incompatible fields, and bodies larger than 16 KiB are rejected. ### Behavioral instructions examples The `instructions` field should describe **how the agent behaves**, not how users should ask. Focus on: ```json { "version": 1, "instructions": "Respondé en español rioplatense. Interpreta las consultas de forma natural: si el usuario dice 'listar consorcios', 'mis consorcios', 'qué consorcios tengo' o 'mostrame los consorcios', todas significan lo mismo. Nunca inventes datos que no devuelvan las capabilities. Si una consulta es ambigua, pedí aclaración en lugar de asumir. Protegé datos sensibles: nunca expongas IDs internos, hashes, o información de autenticación.", "allowed_topics": [ { "name": "consorcios", "description": "Gestión de consorcios: listado, unidades, gastos, pagos y liquidaciones." }, { "name": "cuenta_corriente", "description": "Saldos, deudas y movimientos de cuenta corriente." } ], "out_of_scope_response": "No puedo ayudarte con esa consulta." } ``` ### What to put in instructions | Dimension | ✅ Do | ❌ Don't | |-----------|-------|---------| | Language | "Respondé en español rioplatense" | "El usuario debe escribir en español" | | Interpretation | "Interpreta 'listar', 'ver', 'mostrar', 'mis' como equivalentes" | "El usuario debe decir 'listar consorcios'" | | Ambiguity | "Si es ambiguo, pedí aclaración" | "El usuario debe ser específico" | | Invention | "Nunca inventes datos no devueltos por capabilities" | "El usuario debe verificar los datos" | | Privacy | "No expongas IDs internos ni hashes" | "El usuario no debe pedir IDs" | ### Topic scope examples Use `allowed_topics` to define which subjects the agent can address. Each topic needs a `name` (unique, case-insensitive) and a `description` that helps the model match user requests: ```json { "allowed_topics": [ { "name": "gastos", "description": "Consulta, creación y pago de gastos del consorcio." }, { "name": "unidades", "description": "Listado y consulta de unidades funcionales." }, { "name": "liquidaciones", "description": "Liquidaciones mensuales y expensas." } ] } ``` When `allowed_topics` is present, the agent only responds to requests matching at least one topic. Requests outside the allowlist use the `out_of_scope_response` or the platform default: ```text No puedo ayudarte con esa consulta. ``` ## Manage the policy ### Create or update ```bash curl -X PUT "$FORGIUM_AGENT_BASE_URL/v1/agent-policy" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @agent-policy.json ``` The response is `201` for the first policy and `200` for a replacement: ```json { "revision": 2, "updated_at": "2026-08-05T14:03:10.123Z", "policy": { "version": 1, "instructions": "Respondé en español rioplatense. Interpreta las consultas de forma natural..." } } ``` ### Read current policy ```bash $ curl "$FORGIUM_AGENT_BASE_URL/v1/agent-policy" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` Returns `404 POLICY_NOT_FOUND` when no policy exists. ### Delete policy ```bash $ curl -X DELETE "$FORGIUM_AGENT_BASE_URL/v1/agent-policy" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` Returns `204` on success, `404 POLICY_NOT_FOUND` if nothing to remove. Deleting the policy restores platform defaults for subsequent runs. ### Error responses | Status | Code | Meaning | |--------|------|---------| | 400 | `INVALID_JSON` | Malformed JSON body. | | 400 | `POLICY_INVALID` | Policy fails validation. | | 400 | `POLICY_EMPTY` | Neither `instructions` nor `allowed_topics` present. | | 401 | `UNAUTHORIZED` | Missing or invalid API key. | | 404 | `POLICY_NOT_FOUND` | No policy exists (GET/DELETE only). | | 405 | `METHOD_NOT_ALLOWED` | HTTP method not supported. | | 413 | `REQUEST_TOO_LARGE` | Body exceeds 16 KiB. | | 503 | `POLICY_UNAVAILABLE` | Temporary storage failure; retry later. | ## Runtime behavior and limitation The runtime places the validated policy after the platform system prompt and before the capability-availability instruction. The policy tells the model how to behave, not how users should express themselves. User content, images, converted documents, and tool output are untrusted data and cannot modify the policy. Allowlist matching is model-instruction behavior in this release. It is best-effort and is **not** a deterministic moderation service, content filter, or safety boundary. Do not rely on it as the only authorization or safety control for sensitive operations. Capabilities remain independently scoped and protected by their normal approval and execution safeguards. ## Common mistakes | Mistake | Problem | Fix | |---------|---------|-----| | "El usuario debe decir 'listar consorcios'" | Restricts user expression | "Interpreta 'listar', 'ver', 'mostrar', 'mis' como equivalentes" | | "El usuario debe ser específico" | Blames user for ambiguity | "Si es ambiguo, pedí aclaración" | | "El usuario no debe pedir IDs" | Restricts user requests | "Nunca expongas IDs internos" | | "El usuario debe verificar los datos" | Shifts responsibility | "Nunca inventes datos" | | Instructions in English for Spanish users | Language mismatch | Write instructions in the agent's response language | | Overly restrictive topic scope | Agent rejects valid requests | Use broad topic descriptions | --- END docs/integration/agent-policy.md --- --- BEGIN docs/integration/diagnostics.md --- # Capability-gap diagnostics Forgium can expose unresolved capability diagnostics to the authenticated account. These diagnostics help identify requests that the agent could not resolve and decide which capability to implement next. Diagnostics are strictly account-scoped. The API key determines the account; the caller cannot select another account through the request. ## Privacy modes Configure the diagnostic response mode with: ```http PUT /v1/diagnostics/settings Authorization: Bearer $FORGIUM_AGENT_API_KEY Content-Type: application/json ``` ```json { "diagnostic_message_mode": "metadata" } ``` The supported modes are: | Mode | Behavior | |---|---| | `metadata` | Default. Returns category, correlation IDs, capability information, and timestamps; does not return message text. | | `text` | Includes the original user message when it is still available under the platform message-retention policy. | | `disabled` | Returns an empty account-facing report. Internal sanitized audit events and the separate conversation storage policy are unchanged. | Read the current setting with: ```bash curl "$FORGIUM_AGENT_BASE_URL/v1/diagnostics/settings" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` Set `metadata` again to stop including message text in future diagnostic responses, or `disabled` to stop the account-facing report entirely. This setting controls the diagnostic report; it does not change the separate conversation message-storage policy or internal audit retention. ## List capability gaps ```bash curl "$FORGIUM_AGENT_BASE_URL/v1/diagnostics/capability-gaps?limit=50" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` Example in metadata mode: ```json { "message_mode": "metadata", "has_more": false, "events": [ { "id": "are_abc123", "agent_run_id": "run_abc123", "message_id": "msg_abc123", "category": "coverage_gap", "capability": null, "revision": null, "available_capabilities": ["list_consorcios", "list_unidades"], "created_at": "2026-09-02T14:34:51.000Z" } ] } ``` When text mode is enabled, a retained message may be included: ```json { "message_mode": "text", "has_more": false, "events": [ { "id": "are_abc123", "agent_run_id": "run_abc123", "message_id": "msg_abc123", "category": "coverage_gap", "capability": null, "revision": null, "available_capabilities": ["list_consorcios", "list_unidades"], "conversation_id": "conv_abc123", "message": "listar los propietarios de ese consorcio", "redacted": false, "truncated": false, "created_at": "2026-09-02T14:34:51.000Z" } ] } ``` ## Interpreting categories | Category | Meaning | Suggested action | |---|---|---| | `coverage_gap` | The selection flow reported that no eligible capability could fulfill the request. | Review the message, then create or extend a read capability if the request is in scope. | | `agent_dead_end` | The agent could not safely resolve the request. | Review the message and capability descriptions; it may be a routing problem or a missing capability. | These categories are operational hints, not verified classifications of user intent. Confirm the need against the account's product scope before implementing a capability. ## Pagination and retention Results are returned newest first. For the next page, pass the last event's `created_at` and `id` as `before` and `before_id`: ```http GET /v1/diagnostics/capability-gaps?before=2026-09-02T14:34:51.000Z&before_id=are_abc123 ``` Capability audit events follow the platform's 90-day audit-retention policy. Message text follows the separate message-retention policy and may no longer be available even while its diagnostic event remains. The endpoint never returns messages, diagnostics, or capability data belonging to another account. When text mode is enabled, token-shaped values such as bearer tokens, API-key assignments, and common key prefixes are redacted before the response. Do not use diagnostic text as a secret store. --- END docs/integration/diagnostics.md --- --- BEGIN docs/integration/auth.md --- # Authentication — API key The Forgium administrator provides an API key for each environment. Use it with the Public API (`/v1/*`): ```http Authorization: Bearer ``` ## Obtain access Request these values from the Forgium administrator: ```bash FORGIUM_AGENT_BASE_URL= FORGIUM_AGENT_API_KEY= ``` Store the key in the host application's secret manager. Do not commit it, include it in a capability manifest, or put it in logs. If a request returns `401`, ask the administrator to verify or replace the key. ## Forgium-Context verification Capability endpoints receive a short-lived `Forgium-Context` Ed25519 JWS. You **must** validate its issuer, audience, `kid`, time claims, exact method and URL, body hash, and replay ID before accepting a request. ### Quick start with the reference adapter The easiest way to verify Forgium-Context is the framework-neutral reference adapter. It works with any HTTP framework (Express, Hono, Fastify, Workers): ```typescript import { verifyForgiumContext, createInMemoryReplayStore } from "@micro-agent/capabilities/capability-context-reference"; // In your HTTP handler: const result = await verifyForgiumContext({ contextHeader: request.headers.get("Forgium-Context"), method: request.method, url: request.url, body: await request.text(), jwks: await fetchJwks(), // See "Retrieving JWKS" below issuer: "https://api.forgium.dev", audience: "https://your-endpoint.example", // Required: verification fails closed without an atomic replay store. rememberJti: async (jti, expiresAt) => { // Store in KV with TTL (see "Replay protection" below) const existing = await env.KV.get(`jti:${jti}`); if (existing) return false; await env.KV.put(`jti:${jti}`, "1", { expiration: expiresAt }); return true; }, }); if (!result.ok) { // result.code is a stable error code safe for logging return new Response(JSON.stringify({ error: result.code }), { status: 401, headers: { "Content-Type": "application/json" }, }); } // Use result.claims for business logic const { subject_id, capability_id, revision } = result.claims; ``` ### Stable error codes The adapter returns one of these codes on failure: | Code | Meaning | |---|---| | `CONTEXT_MISSING_HEADER` | Forgium-Context header is missing or empty | | `CONTEXT_INVALID_JWS` | Token is not a valid compact JWS | | `CONTEXT_INVALID_HEADER` | Protected header is invalid | | `CONTEXT_UNKNOWN_KEY` | `kid` not found in JWKS | | `CONTEXT_INVALID_CLAIMS` | Claims structure is invalid | | `CONTEXT_CLAIMS_MISMATCH` | Claims don't match the request | | `CONTEXT_EXPIRED` | Token has expired (TTL is 5 minutes) | | `CONTEXT_INVALID_SIGNATURE` | Signature verification failed | | `CONTEXT_BODY_HASH_MISMATCH` | Body hash doesn't match claims | | `CONTEXT_REPLAY_PROTECTION_REQUIRED` | Replay store callback was not configured | | `CONTEXT_REPLAY` | JTI has been seen before | These codes are safe to log and return to clients. Never log the JWS token, claims payload, or request body. ### Retrieving JWKS Retrieve verification keys from `/.well-known/forgium/capability-context/v1/jwks.json`. Cache the response and refresh when `kid` lookup fails. ```typescript async function fetchJwks(): Promise { const response = await fetch( "https://api.forgium.dev/.well-known/forgium/capability-context/v1/jwks.json" ); const { keys } = await response.json(); return keys; } ``` **Key rotation:** The JWKS may contain multiple keys. The adapter selects the key matching the token's `kid` header. When Forgium rotates keys, the JWKS will include both old and new keys during the transition period. ### Replay protection You **must** implement replay protection. The JTI (JWT ID) is unique per token. Store seen JTIs with TTL equal to the token's expiration. **Production options:** - **Cloudflare KV:** Store with `expiration` option - **D1:** Insert with TTL, check for existence - **Durable Object:** Use storage with alarm-based cleanup **Testing/development:** ```typescript import { createInMemoryReplayStore } from "@micro-agent/capabilities/capability-context-reference"; const { rememberJti, cleanup } = createInMemoryReplayStore(); // Pass rememberJti to verifyForgiumContext // Call cleanup() when shutting down ``` ### What the host must verify The Forgium-Context contains these claims that must match the request: | Claim | Must match | |---|---| | `iss` | Your expected issuer (e.g., `https://api.forgium.dev`) | | `aud` | Your endpoint URL or audience identifier | | `method` | The HTTP method (e.g., `POST`) | | `url` | The exact request URL | | `body_sha256` | SHA-256 hash of the raw request body | | `iat` / `exp` | Current time (with 60s clock skew tolerance) | | `jti` | Unique ID (for replay protection) | Additional claims available after verification: - `subject_id`: The user's opaque identifier - `conversation_id`: The conversation ID - `capability_id`: The capability that was called - `revision`: The capability revision ### Security requirements 1. **Always verify the signature.** Never skip cryptographic verification. 2. **Always check replay.** Without replay protection, a captured token can be reused. 3. **Validate all claims.** Don't cherry-pick claims to verify. 4. **Stop on failure.** If verification fails, reject the request entirely. 5. **Never log secrets.** The JWS token, claims, and body are sensitive. ### Example with Express ```typescript import express from "express"; import { verifyForgiumContext } from "@micro-agent/capabilities/capability-context-reference"; const app = express(); app.post("/v1/people/lookup", express.raw({ type: "application/json" }), async (req, res) => { const result = await verifyForgiumContext({ contextHeader: req.headers["forgium-context"], method: req.method, url: `${req.protocol}://${req.get("host")}${req.originalUrl}`, body: req.body.toString(), jwks: await fetchJwks(), issuer: "https://api.forgium.dev", audience: `${req.protocol}://${req.get("host")}`, rememberJti: async (jti, expiresAt) => { // Your replay store implementation }, }); if (!result.ok) { return res.status(401).json({ error: result.code }); } // Your business logic here const { subject_id } = result.claims; // ... }); ``` ### Example with Cloudflare Workers ```typescript import { verifyForgiumContext } from "@micro-agent/capabilities/capability-context-reference"; export default { async fetch(request: Request, env: Env): Promise { const result = await verifyForgiumContext({ contextHeader: request.headers.get("Forgium-Context"), method: request.method, url: request.url, body: await request.text(), jwks: await fetchJwks(), issuer: "https://api.forgium.dev", audience: new URL(request.url).origin, rememberJti: async (jti, expiresAt) => { const existing = await env.KV.get(`jti:${jti}`); if (existing) return false; await env.KV.put(`jti:${jti}`, "1", { expiration: expiresAt }); return true; }, }); if (!result.ok) { return new Response(JSON.stringify({ error: result.code }), { status: 401, headers: { "Content-Type": "application/json" }, }); } // Your business logic here return new Response(JSON.stringify({ ok: true })); }, }; ``` ## API key authentication Retrieve verification keys from `/.well-known/forgium/capability-context/v1/jwks.json`; never request or store a private signing key. Rotate endpoint tokens with the [token rotation runbook](token-rotation.md). ## Example ```bash curl -fsS "$FORGIUM_AGENT_BASE_URL/v1/capabilities" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` --- END docs/integration/auth.md --- --- BEGIN docs/integration/errors.md --- # API errors The Public API uses JSON errors. The OpenAPI contract is authoritative for the status and shape of each route. Most Public API routes return: ```json { "error": "Invalid API key", "code": "UNAUTHORIZED" } ``` The HTTP channel returns a structured error object: ```json { "error": { "code": "VALIDATION_ERROR", "message": "Invalid JSON body" } } ``` ## Recovery guide | Status | Typical codes | Recovery | |---|---|---| | `400` | `VALIDATION_ERROR`, `INVALID_CREDENTIAL`, `MANIFEST_INVALID` | Fix the request using the OpenAPI schema; do not retry unchanged input. | | `401` | `UNAUTHORIZED` | Confirm the API key and base URL belong to the same environment; ask the administrator to reissue the bundle if needed. | | `404` | `CAPABILITY_NOT_FOUND`, `CREDENTIAL_NOT_FOUND` | Confirm the identifier belongs to the current access bundle. | | `405` | `METHOD_NOT_ALLOWED` | Use the method documented for the route. | | `409` | `CONFLICT`, `IDEMPOTENCY_KEY_REUSED`, `REVISION_CONFLICT`, `CAPABILITY_DISABLED`, `CREDENTIAL_NOT_CONFIGURED` | Re-read the capability, use the current `If-Match` revision for an update or explicit state transition, and retry only after correcting the request. | | `502` | `CAPABILITY_EXECUTION_UNAVAILABLE`, `UPSTREAM_*` | Inspect the bounded error body. Forgium never executes host-owned operations; `UPSTREAM_*` codes indicate endpoint or transport failure. | | `413` | `REQUEST_TOO_LARGE` | Reduce the credential request to the documented limit. | | `422` | `VALIDATION_ERROR` | Send a text message with a non-empty body and valid fields. | | `500` | `AGENT_RUNTIME_ERROR`, `INTERNAL_ERROR` | Apply the host application's retry policy and retain the response code for support. | | `503` | `CREDENTIAL_VAULT_UNAVAILABLE`, `POLICY_UNAVAILABLE` | Do not retry credential rotation repeatedly; retry other temporary service failures according to the host policy and contact the operator if they persist. | Never log API keys, endpoint tokens, authorization headers, or complete request bodies containing secrets. For a suspected leak, stop and request rotation. ## Capability runtime behavior These are observable agent behaviors, not HTTP error codes. They describe how the agent handles capability-related situations in conversation. ### Missing arguments When the user's message does not include a required argument for a capability, the agent should ask for clarification rather than fabricating the value. ```text User: "Obtener las unidades funcionales." Agent: "¿De qué consorcio querés consultar las unidades?" ``` The agent must not invent IDs, names, or other operational arguments to fill missing fields. ### No matching capability selected When the user's request does not match any active capability (by topic, intent, or available schema), the agent responds without calling a capability. This is not an error — it is the expected behavior for out-of-scope or unsupported requests. ```text User: "¿Cuál es la capital de Uruguay?" Agent: "La capital de Uruguay es Montevideo." ``` If a model proposes a capability whose declared explicit exclusion matches the request, the runtime also fails closed without contacting the endpoint. The trace shows the selected candidate, an unresolved `coverage_gap`, and the terminal `safe_fallback`; the exclusion cannot broaden authorization or change account policy. ### Capability execution failure When the upstream endpoint returns an error (timeout, 5xx, unauthorized), the agent communicates the unavailability without inventing data. ```text User: "Obtener las unidades del consorcio Torres del Agua." Agent: "No pude consultar esa información en este momento. Por favor, intentá de nuevo." ``` The agent must not claim the consorcio does not exist, fabricate units, or expose internal error codes to the user. ### Host-owned operation result Forgium does not approve or execute mutations. The host application owns confirmation, authorization, concurrency, idempotency, and mutation. It may report a terminal status through the operation-results endpoint after the host operation finishes. ### Empty result When the capability returns a valid but empty result (e.g., a consorcio exists but has no units), the agent reports the absence without treating it as an error. ```text User: "Obtener las unidades del consorcio Torres del Parque." Agent: "No hay unidades registradas para Torres del Parque." ``` ### One capability per turn The current runtime executes at most one capability per turn. This is a platform limit, not an error. If a request needs another operation, only the first matching capability is executed in the current turn. Structure the conversation so each turn requests a single operation. ### Current-run diagnostics When present in a successful HTTP-channel response, `capability_trace.events` uses stable statuses rather than model reasoning: `not_selected`, `unresolved`, `selected`, `arguments_invalid`, `execution_failed`, `empty_result`, `result_received`, `response_generated`, and `runtime_failed`. A `reason_code` is a bounded technical category, not an explanation of why a model made a decision. In particular, `safe_fallback` identifies the terminal safe-response path, not its cause; inspect the preceding event. A `runtime_failed` event distinguishes `selection_inference_failed` from `grounded_inference_failed`. The trace never includes prompts, chain-of-thought, message text, arguments, result bodies, credentials, complete signed context values, headers, or stack traces. It covers only the run returned by that response; use the opaque `run_id` or `benchmark_observation.correlation_id` when contacting support. See [Current-run capability diagnostics](capability-handoff.md#current-run-capability-diagnostics) for the complete status, reason-code, selection, grounding, and diagnostic semantics. --- END docs/integration/errors.md --- --- BEGIN docs/integration/idempotency.md --- # Idempotency ## Capability imports Every `POST /v1/capabilities/import` request requires an `Idempotency-Key` header. Generate one key for a single logical import and reuse that exact key when retrying the same request. ```bash -H "Idempotency-Key: capability-import-$(date +%s)" ``` A repeated import with the same key returns the previous result and includes `Idempotency-Replayed: true`; it does not create a second capability revision. Use a new key only for a new logical import, such as one containing a revised manifest. ## Host operation results Host-owned operation reports to `POST /v1/conversations/{conversationId}/operation-results` require an `Idempotency-Key`. The key is scoped to the account and stored with a request hash. Reusing it with a different interaction, operation, or status returns `409 IDEMPOTENCY_KEY_REUSED`. Forgium does not execute or retry the host operation. The host remains responsible for authorization, concurrency, mutation idempotency, and replay protection before reporting a terminal status. --- END docs/integration/idempotency.md --- --- BEGIN docs/integration/token-rotation.md --- # Capability endpoint token rotation runbook Endpoint tokens are environment-specific secrets. Use a different token for development, staging, and production; never copy a staging bundle into production. ## Planned rotation 1. Generate a new scoped token in the host application's secret manager. 2. Deploy the host endpoint so it accepts the old and new token during the short, documented grace period. 3. Store the new value with `PUT /v1/capability-credentials/{credentialRef}`. Forgium encrypts it with the environment vault key and never returns it. 4. Run the current capability contract test and record the capability revision, environment, timestamp, and result code. 5. Confirm a synthetic read or host-interaction preparation reaches the host endpoint with the new token. 6. After the grace period, revoke the old token in the host secret manager and verify that it receives `401`. 7. Record the operator, expiry of the grace period, and evidence without recording either token. The update is scoped to the authenticated access bundle and does not change a capability URL or its revision. If a capability should stop using a credential immediately, call: ```bash curl -fsS -X DELETE "$FORGIUM_AGENT_BASE_URL/v1/capability-credentials/$CREDENTIAL_REF" \ -H "Authorization: Bearer $FORGIUM_AGENT_API_KEY" ``` The response is `{ "credential_ref": "...", "status": "revoked" }`. A revoked credential blocks new endpoint calls and must be replaced before enabling the capability for traffic. ## Suspected leak Stop using the affected token, revoke it at the host endpoint, revoke the Forgium credential, issue a replacement, and inspect the security event trail for the affected capability. Do not paste the token into tickets, logs, chat, or a capability manifest. Rotate the Forgium API key or context signing key separately if either platform secret may also be exposed. ## Ownership and evidence The host application owns endpoint-token generation, endpoint acceptance, and old-token revocation. Forgium Operations owns vault storage, API-key bundles, and signing-key publication. Product/Security owns release approval. Evidence must contain only environment, credential reference, revision, stable result codes, and timestamps. --- END docs/integration/token-rotation.md --- --- BEGIN docs/integration/privacy-and-retention.md --- # Privacy and retention policy for the integration MVP This policy describes the minimum handling of data sent through Forgium. The host application remains responsible for its own user notice, lawful basis, access controls, and deletion obligations. ## Data classes | Class | Stored by Forgium | Purpose | Default retention | | --- | --- | --- | --- | | Message text | Conversation and message records | Generate and audit a reply | 30 days | | Host-interaction correlation | Forgium interaction ID, account/conversation/subject scope, operation name, interaction type, status, expiry, and timestamps | Accept an optional terminal report without retaining host references | 90 days | | Capability and service audit | IDs, SHA-256 hashes, timing, HTTP status, stable error codes, unresolved-request and failure categories | Security and operations | 90 days | | AI usage ledger | Opaque account/user/correlation IDs, model, bounded parameters, token counts when reported, versioned rates, costs, and lifecycle status | Internal AI cost attribution and future account costing | No public retention period announced in this version | | Credentials | AES-GCM ciphertext only | Authenticate an endpoint | Until revoked, then 30 days for deletion evidence | | Signing material | Private key in the Worker secret store only | Sign outbound context | Managed by key-rotation policy; never stored in D1 | Bodies, bearer tokens, complete JWS values, capability arguments, previews, `operation_ref`, `selection_ref`, and `option_ref` are not written to capability audit or host-interaction records. Staging fixtures use synthetic data only. AI usage records never contain prompts, responses, message text, attachments, schemas, tool definitions, raw provider payloads, credentials, or JWS values. Chat `user_id` values are opaque external identifiers and remain optional during client migration. Prompt Integrations are account-only for usage attribution. ## Capability-audit retention enforcement `agent_run_events` are deleted daily by the dedicated retention Worker after 90 days. They include sanitized capability decisions and service-audit events; they never duplicate message text. Unresolved-request categories are bounded operational signals, not authorization decisions or verified explanations of user intent. The worker has only a D1 binding, no public route, and deletes in bounded idempotent batches. `Platform On-call` owns execution monitoring and escalates failures to the Platform owner. Accounts may choose whether their account-scoped capability-gap diagnostic endpoint returns message text with `PUT /v1/diagnostics/settings`. The safe default is `diagnostic_message_mode: "metadata"`; `"text"` is opt-in. This setting controls diagnostic projection. `"disabled"` makes the account-facing report empty. Neither setting changes the separate conversation message-storage policy or internal audit retention. Text remains subject to the message retention period and is never copied into `agent_run_events`. An active legal hold or incident hold excludes the specific event from deletion until Operations/Security releases it under the incident process. Holds do not pause retention for other records. Retention-worker logs carry only a cutoff, deleted count, and batch count. ## Access, deletion, and support Access is limited to the authenticated host backend, the Forgium runtime for execution, and authorized Operations/Security personnel for incident response. Support access is time-limited, logged, and must use redacted identifiers. The host application can request deletion of conversations, messages, host-interaction correlation records, credentials, and related audit records through the operational owner. Deletion requests are acknowledged with scope, timestamp, requester, and completion evidence. Legal holds or active incident investigations may delay deletions; the requester receives the reason and expected review date. ## Incidents For suspected disclosure, the host application must revoke the endpoint token and Forgium access bundle, preserve redacted evidence, and contact the platform incident owner. Forgium Operations rotates affected credentials or signing keys, reviews security events, and communicates impact and recovery steps. Never include secrets or raw message bodies in an incident ticket. This MVP policy requires Product, Legal, and Security approval before a production access bundle is issued. Changes to retention, data classes, or support access require a documented review. --- END docs/integration/privacy-and-retention.md --- --- BEGIN docs/integration/compatibility-and-versioning.md --- # Compatibility and versioning Forgium versions its published public contract with [Semantic Versioning](https://semver.org/). The authoritative version is `info.version` in [the OpenAPI contract](api.openapi.yaml), which must match `contract_version` in `agent-manifest.json`. The changelog records every released public-contract change. It is not a deployment log and does not describe internal-only changes. ## Compatibility policy Within a major version, existing clients that send valid requests and consume documented responses continue to work without a code change. | Change | Version increment | | --- | --- | | Documentation clarification or internal implementation change that does not alter the public contract | Patch | | Backward-compatible addition, such as a new optional request property that the service does not require existing clients to send | Minor | | Removal or rename of a route, operation, property, enum value, error code, or capability lifecycle value | Major | | Change to a property's type, format, validation bounds, required status, default, or semantics | Major | | New property emitted in a response object with `additionalProperties: false` | Major | | New required property in any closed object, or rejecting a previously accepted property | Major | Enum additions follow the same rule as response-shape additions when the enum is published as a closed OpenAPI enum. Protocol enums (for example action and capability lifecycle states) are therefore not extended within a major version. Diagnostic codes are intended to be parsed tolerantly: their stable catalog may grow without changing the meaning of existing codes, but clients must not treat an unknown diagnostic code as impossible. Closed objects (`additionalProperties: false`) are intentionally strict. A client may validate response objects as strictly as the published schema, so a new response property can break it even when that property is optional in the new schema. Such changes require a new major contract version, a migration guide, and a supported coexistence period or replacement route. For a minor release, consumers should still regenerate types and review the changelog before adopting optional additions. Clients must not rely on undocumented fields, ordering, or platform-default text. ## Contract 5 capability boundary Contract 5 capabilities are read-only. Forgium rejects mutation capabilities, approval metadata, candidate-resolution metadata, and legacy action or resolution workflows. Use `result_handling: "host_interaction"` when a host application needs a bounded preparation result; the host owns confirmation, selection, authorization, idempotency, and any mutation. ## Changelog ### [7.0.0] - 2026-09-11 - Removes the `draft → test → active` capability lifecycle and the `/activate` operation. - Imports and complete validated replacement revisions publish atomically as `active`; the only persisted lifecycle states are `active` and `disabled`. - Adds explicit revision-guarded `/enable` and `/disable` operations. `PATCH` preserves lifecycle state, capability IDs remain immutable, and contract tests are independent records that do not change state or revision. - Extends the private Admin service-audit projection with policy revisions, sanitized per-capability decisions, and terminal fallback phase; the public `/messages` contract is unchanged. - Adds the diagnostic `business_outcome_failed` trace reason for capabilities that opt into the closed outcome contract; mutation execution remains host-owned. ### [6.0.1] - 2026-09-08 - Clarifies that host-operation preparation is a just-in-time preflight and that the host must validate eligibility during preparation and again before mutation. - Clarifies that `not_found` means zero currently eligible operations and covers both no matching resource and matching-but-ineligible resources. - Replaces the potentially misleading `not_found` assistant text with the neutral deterministic response “No hay una operación disponible para esta consulta.” ### [6.0.0] - 2026-09-05 - Replaces the unused JSON/base64 image attachment shape with the binary `multipart/form-data` contract using `payload` and `image` parts. - Adds one-image JPEG, PNG, and WebP transport guidance with a 4 MiB limit. - Documents multi-turn image-assisted operation preparation, bounded pending drafts, cancellation, and host-owned confirmation. ### [5.2.0] - 2026-09-03 - Adds optional `context_bindings` metadata so capabilities can declare which validated reference fields and producer capabilities may fill a name-like input. - Makes routing candidate selection bounded without requiring exact word overlap; capability descriptions, schemas, examples, and validated context remain the semantic sources for model selection. - Keeps deterministic contextual recovery fail-closed for negation, conflicting entities, ambiguous references, incompatible provenance, and invalid constructed arguments. ### [5.1.1] - 2026-09-03 - Adds fail-closed deterministic recovery for a bounded contextual read when every routing model omits a tool call and exactly one eligible capability can be completed from provenance-compatible validated references. - Clarifies that explicit entities, ambiguous references or capabilities, incompatible scopes, routing exclusions, and invalid arguments prevent contextual recovery. ### [5.1.0] - 2026-09-02 - Adds account-isolated capability-gap diagnostics for `coverage_gap` and `agent_dead_end` events. - Adds the account-owned `diagnostic_message_mode` setting, with metadata as the default, message text as an explicit opt-in projection, and disabled as an opt-out of the account-facing report. - Documents that diagnostic projection does not change the separate core conversation message-storage policy. ### [5.0.1] - 2026-09-01 - Publishes the exact confirmation, selection, and not-found preparation bodies, bounds, and canonical `host_interaction` output schema. - Makes capability contract tests enforce the same discriminated host-interaction parser used by conversational runtime. - Documents deterministic contract-test scope, not-found behavior, interaction expiry, correlation retention, and opaque-reference non-persistence guarantees. ### [5.0.0] - 2026-08-31 - Removes the 4.x action and resolution routes and response fields. - Makes public capabilities read-only and rejects mutation and approval metadata during import and activation. - Adds the host-owned `host_interaction` result-handling mode for bounded confirmation, selection, and not-found preparation results. - Adds the operation-results callback for hosts to report terminal outcomes without transferring operation ownership to Forgium. ### [4.1.0] - 2026-08-31 - Defines the optimistic-concurrency convention for mutations over existing resources: capabilities use a public resource identifier and monotonic expected revision, checked atomically by the host endpoint. - Defines `RESOURCE_CHANGED` as a terminal, non-retryable stale-resource conflict. Reconfirmation creates a new action and idempotency key; the old action's arguments are never changed. ### [4.0.0] - 2026-08-28 - Adds structured candidate resolution for capabilities that declare `resolution_capabilities`. Multiple candidates are returned as `pending_resolution`; `POST /v1/resolutions/{resolution_id}/select` creates a separate `pending_action` and never executes the mutation. - Defines resolver output as `{ candidates: [{ label, arguments }] }`. Forgium keeps candidate arguments server-side and exposes only opaque option IDs and labels to the UI. ### [3.0.0] - 2026-08-28 - Adds optional `resolution_capabilities` to capability definitions. It lists read-only fallback helpers for mutations that require a canonical identifier when the mutation endpoint cannot resolve the user's alias directly. The mutation endpoint remains the preferred fast path; the field does not require an immediate previous call, guarantee helper invocation, or authorize a side effect. - Removes `requires_previous_capability`. It was not a reliable workflow precondition and is no longer accepted in manifests or capability records. ### [2.7.2] - 2026-08-25 - Documents the fail-closed routing guard for explicit capability exclusion clauses. A matching exclusion prevents executor I/O and returns the bounded unresolved safe fallback; account authorization and policy remain authoritative. ### [2.7.1] - 2026-08-25 - Documents optional-but-recommended opaque `user_id` attribution for HTTP channel chat without imposing a migration deadline. - Clarifies that Prompt Integration AI usage is attributed to its account and does not require a user identifier. - Documents the internal AI usage ledger's content-free audit and versioned pricing behavior; no usage or cost fields are added to public responses. ### [2.7.0] - 2026-08-24 - Adds `POST /v1/capabilities` for creating a single capability as a draft without wrapping it in an array. This is the recommended path for adding one capability; `POST /v1/capabilities/import` remains available for bulk creation. - Adds `CapabilityCreateRequest` schema with `domain` and `capability` (single object) to the OpenAPI contract. ### [2.6.0] - 2026-08-19 - Extends current-run capability diagnostics with sanitized `unresolved` and `runtime_failed` statuses, including stage-specific selection and grounded inference failure categories. - Documents the complete capability-trace status and reason-code semantics, selection and grounded-generation criteria, and a correlation-based diagnostic checklist. ### [2.5.0] - 2026-08-19 - Adds `pattern` to the bounded `input_schema` keyword matrix for string arguments that declare `maxLength`. Patterns use ECMAScript regular-expression syntax and are enforced before a capability invocation. - Keeps `pattern` unsupported for `output_schema`; capability endpoints remain authoritative for validation, authorization, and business rules. ### [2.4.1] - 2026-08-12 - Defines the current Public API contract, including HTTP messaging, capabilities, agent policies, actions, and prompt integrations. - Establishes this compatibility and versioning policy. No endpoint or schema behavior changes are introduced by this documentation entry. ## Upgrade process Before using a new contract version: 1. Retrieve `agent-manifest.json` from the supplied Forgium base URL and compare `contract_version` with the version your integration supports. 2. Read the changelog and any linked migration guide. 3. Retrieve and validate the matching [OpenAPI contract](contract-retrieval.md). 4. Run contract tests in staging before production rollout. If a major version is not supported by your integration, keep using the documented compatible version during its announced support window and contact the Forgium administrator for the migration path. --- END docs/integration/compatibility-and-versioning.md --- --- BEGIN docs/integration/migrate-agent-contract-4-to-5.md --- # Migrating from Agent API 4.x to 5.0 > **Release 5.0.1:** the breaking cutover is complete. The 4.x action and > resolution routes are no longer available. ## What changes Version 5.0 replaces Forgium-managed mutation approvals with host-managed interactions. Forgium still resolves intent and calls a configured read-only preparation capability, but the host application owns selection, confirmation, authorization, idempotency, concurrency, and the mutation itself. The new preparation capability declares: ```json { "result_handling": "host_interaction", "side_effect": "read", "requires_approval": false, "retry_count": 0, "cache_ttl_seconds": 0 } ``` Its endpoint returns exactly one of `confirmation_required`, `selection_required`, or `not_found`. The Agent API returns a closed `interaction` union with `owner: "host"` and either `type: "confirm_operation"` or `type: "select_option"`. The exact preparation response bodies, bounds, canonical `output_schema`, and contract-test behavior are documented in the [host-managed interaction reference](capability-handoff.md#host-managed-interactions). In particular, `not_found` is exactly `{ "status": "not_found" }`; ambiguity must return `selection_required` with two to eight host-bounded options. `not_found` means zero currently eligible operations, not necessarily zero matching resources. It is also correct when matching resources exist but their current state makes the requested operation ineligible. Forgium calls this read-only endpoint as a just-in-time preflight after detecting the user's intent; the host must check eligibility then and repeat the check before the eventual mutation. This is a host-side data-resolution rule, not a Forgium routing rule: the host adapter must test the number of eligible matches before choosing the status. Forgium checks that the selected branch is structurally valid, but it cannot detect a false `not_found` produced for an ambiguous business query. Contract 5 intentionally removes the 4.x mutation-test contract: capability definitions no longer accept `test`/`dry_run` or `idempotency_header`, and `CapabilityTestRequest` no longer accepts `expect`. Contract tests exercise a real active endpoint at its current revision under deterministic test scope, accept any valid preparation branch, and may allocate only a temporary preparation record; they must never execute a business mutation. ## Client migration 1. Keep sending ordinary messages to `POST /v1/channels/http/messages`. 2. Route by `interaction.owner` and `interaction.type`; do not inspect the assistant text or capability name. 3. Send selections and confirmations to the host backend, not to Forgium. Forgium does not return callback URLs and the client must not expose its Forgium API key to the host interaction UI. 4. Optionally report the terminal host outcome to `POST /v1/conversations/{conversationId}/operation-results` using the account API key and an `Idempotency-Key`. The report body is limited to: ```json { "interaction_id": "int_123", "operation": "cancel_expense", "status": "succeeded" } ``` `status` is one of `succeeded`, `resource_changed`, `rejected`, or `failed`. Do not send operation references, mutation arguments, result bodies, or user-authored error messages. ## Server migration sequence 1. Add and contract-test the read-only preparation capabilities while the existing 4.x contract is still active. 2. Implement host-owned selection and confirmation with subject binding, expiry, replay protection, idempotency, and atomic material-revision checks. A stale revision must return `resource_changed` without mutating. 3. Deploy the host backend and client routing before enabling the preparation capability for migrated accounts. 4. Expire any historical 4.x pending records and record the cutover evidence. Do not convert their stored arguments into host interactions. 5. Switch clients to the latest 5.0.x manifest and OpenAPI contract. Existing legacy tables may remain as retention data, but their public routes are removed and dormant tables do not imply that the old workflows remain supported. ## Security boundaries Opaque operation, selection, and option references may appear in the host's preparation response and in the public `interaction` envelope. They must not appear in Workers AI prompts, logs, traces, audit records, conversation metadata, or Forgium's interaction metadata. Render preview values as text. The host remains authoritative for authorization and concurrency; a successful preparation never authorizes a mutation by itself. --- END docs/integration/migrate-agent-contract-4-to-5.md --- --- BEGIN docs/integration/prompt-integrations.md --- # Browser prompt integrations Use a prompt integration when an operator has given you an endpoint, an origin, an input schema, and a public Turnstile sitekey. This is a frontend integration only: do not add an API key, prompt, model name, or secret to the browser code. ## Integration bundle The operator provides an integration bundle containing: | Field | Description | |---|---| | `endpoint` | HTTPS URL to send requests to | | `origin` | Configured HTTPS origin (must match exactly) | | `turnstile_sitekey` | Public Turnstile sitekey for rendering the widget | | `turnstile_action` | Action string required by Turnstile verification | | `input_schema` | JSON Schema describing accepted input fields | ## Rendering Turnstile Render the Turnstile widget using the provided sitekey **and** action. The action is mandatory — the server rejects tokens that do not match the configured action. ```html ``` The `action` value is returned in the admin API response when the integration is created. Hardcoding the action in the browser code is safe — it is not a secret. ## Request Send the declared form data and the single-use token to the provided endpoint: ```js const response = await fetch(integration.endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ input: { business_context: form.businessContext.value }, turnstile_token: token, }), }); const body = await response.json(); if (!response.ok) throw new Error(body.error?.code ?? "PROMPT_INTEGRATION_FAILED"); renderResult(body.data); ``` The `input` object must match the schema returned with the integration bundle. The response is always JSON and `data` matches the configured output schema. Treat both the form values and returned data as untrusted content. Prompt Integration usage is attributed to the account that owns the integration. This browser-only route does not accept or require a `user_id`; do not add one to the request for billing purposes. ## Retry and failure handling Turnstile tokens are single-use. Reset the widget after every submission, including failures, before allowing another attempt. Do not retry a `VALIDATION_ERROR`, `ORIGIN_DENIED`, or `TURNSTILE_INVALID` response without fixing the request or obtaining a new token. Respect `Retry-After` for `RATE_LIMITED` and `DAILY_QUOTA_EXCEEDED`. A `404 INTEGRATION_DISABLED` means the operator has disabled the endpoint. ## Contract The public OpenAPI document is the source of truth for the execution route: `api.openapi.yaml`. The endpoint is browser-only, accepts no credentials, and does not provide a server-to-server integration mode. --- END docs/integration/prompt-integrations.md --- --- BEGIN docs/integration/api.openapi.yaml --- openapi: 3.1.0 info: title: Forgium Agent Public API version: 7.0.0 description: | Stable public contract for sending messages to Forgium Agent, managing account policies, and registering read-only host capabilities. Mutations and candidate-resolution workflows are owned by the host application. contact: name: Forgium platform administrator license: name: Forgium public documentation terms identifier: LicenseRef-Forgium-Public-Docs servers: - url: https://{apiHost} description: Use the host from FORGIUM_AGENT_BASE_URL. Do not infer a host from this document. variables: apiHost: default: api.forgium.dev description: Hostname only, copied from FORGIUM_AGENT_BASE_URL. security: - bearerAuth: [] tags: - name: System description: Availability checks. - name: HTTP channel description: Synchronous text messaging. - name: Capabilities description: Self-service host-application capability lifecycle. - name: Agent policy description: Optional account-wide conversational instructions and topic scope. - name: Diagnostics description: Account-scoped capability-gap diagnostics and privacy settings. - name: Prompt integrations description: Browser-only, operator-provisioned synchronous JSON forms. paths: /health: get: operationId: getHealth summary: Check public API availability security: [] tags: [System] responses: '200': description: Public API is available. content: application/json: schema: $ref: '#/components/schemas/HealthResponse' example: status: ok service: agent-api '400': description: Invalid health-check request. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': description: Public API is temporarily unavailable. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /.well-known/forgium/capability-context/v1/jwks.json: get: operationId: getCapabilityContextJwks summary: Retrieve public keys for Forgium capability contexts security: [] tags: [System] responses: '200': description: Public Ed25519 verification keys. content: application/json: schema: $ref: '#/components/schemas/JwksResponse' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/channels/http/messages: post: operationId: sendHttpMessage summary: Send a text message with an optional image and receive a synchronous response tags: [HTTP channel] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HttpChannelRequest' example: user_id: mobile_user_123 message: type: text body: Hello, can you help me? metadata: platform: ios app_version: 1.0.0 multipart/form-data: schema: $ref: '#/components/schemas/HttpChannelMultipartRequest' encoding: payload: contentType: application/json responses: '200': description: Agent response. content: application/json: schema: $ref: '#/components/schemas/HttpChannelResponse' examples: answer: value: conversation_id: conv_abc123def456 message: id: msg_abc123def456 role: assistant type: text body: Hello, how can I help? host_interaction: summary: Response requiring a host-owned confirmation or selection value: conversation_id: conv_abc123def456 message: id: msg_abc123def456 role: assistant type: text body: Encontré la operación. Confirmá si querés continuar. interaction: interaction_id: int_abc123def456 owner: host type: confirm_operation operation: cancel_expense operation_ref: opaque-host-value expires_at: '2026-08-02T16:05:00Z' preview: title: Cancelar gasto fields: - label: Concepto value: Servicio de internet with_capability_trace: summary: Response with capability trace, benchmark observation, and timing. value: conversation_id: conv_abc123def456 message: id: msg_abc123def456 role: assistant type: text body: "El consorcio tiene 12 unidades funcionales." capability_trace: version: v1 run_id: run_abc123def456 message_id: msg_abc123def456 events: - ordinal: 1 stage: selection status: selected capability: list_unidades revision: 3 - ordinal: 2 stage: execution status: result_received capability: list_unidades revision: 3 - ordinal: 3 stage: response_generation status: response_generated reason_code: grounded timing: preparation_ms: 12 selection_inference_ms: 840 selection_inference_attempts: 1 selection_retry_delay_ms: 0 execution_ms: 90 executor_attempts: 1 grounded_inference_ms: 510 e2e_processing_ms: 1490 benchmark_observation: capability: list_unidades argument_category: known fixture_result: non_empty status: 200 model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast" correlation_id: run_abc123def456 '401': $ref: '#/components/responses/Unauthorized' '405': $ref: '#/components/responses/HttpMethodNotAllowed' '409': $ref: '#/components/responses/HttpDuplicateEvent' '413': $ref: '#/components/responses/HttpRequestTooLarge' '422': $ref: '#/components/responses/HttpValidationError' '500': $ref: '#/components/responses/HttpRuntimeError' /v1/conversations/{conversationId}/operation-results: post: operationId: reportHostOperationResult summary: Report a terminal host-owned operation result description: >- Optional host callback for keeping the Forgium conversation current. The host performs authorization and mutation locally; Forgium only validates the bounded terminal status and may append deterministic text. Do not send operation references, mutation arguments, result bodies, or user-authored messages. tags: [HTTP channel] parameters: - name: conversationId in: path required: true schema: type: string minLength: 1 maxLength: 128 - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HostOperationResultRequest' example: interaction_id: int_abc123def456 operation: cancel_expense status: succeeded responses: '200': description: Terminal result accepted or replayed. content: application/json: schema: $ref: '#/components/schemas/HostOperationResultResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '410': $ref: '#/components/responses/Gone' '413': $ref: '#/components/responses/RequestTooLarge' /v1/webhooks/meta: get: operationId: verifyMetaWebhook summary: Verify the Meta WhatsApp webhook callback description: Meta uses this unauthenticated challenge request when configuring the callback URL. security: [] tags: [Webhooks] parameters: - name: hub.mode in: query required: true schema: { type: string, enum: [subscribe] } - name: hub.verify_token in: query required: true schema: { type: string, maxLength: 256 } - name: hub.challenge in: query required: true schema: { type: string, maxLength: 2048 } responses: '200': description: The challenge value is returned as plain text. content: text/plain: schema: { type: string } '403': $ref: '#/components/responses/MetaWebhookForbidden' post: operationId: receiveMetaWebhook summary: Receive a native Meta WhatsApp event description: Validates Meta's X-Hub-Signature-256 header, routes by the registered phone_number_id, and queues inbound text messages. security: [] tags: [Webhooks] parameters: - name: X-Hub-Signature-256 in: header required: true schema: type: string pattern: '^sha256=[0-9a-f]{64}$' requestBody: required: true content: application/json: schema: type: object additionalProperties: true example: object: whatsapp_business_account entry: [] responses: '200': description: Event accepted, duplicated, or intentionally ignored. content: application/json: schema: $ref: '#/components/schemas/MetaWebhookResponse' '400': $ref: '#/components/responses/MetaWebhookBadRequest' '401': $ref: '#/components/responses/MetaWebhookUnauthorized' '404': $ref: '#/components/responses/MetaWebhookPhoneNotRegistered' '413': $ref: '#/components/responses/MetaWebhookTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/prompt-integrations/{publicId}/execute: options: operationId: preflightPromptIntegration summary: Preflight a browser prompt integration request security: [] tags: [Prompt integrations] parameters: - $ref: '#/components/parameters/PromptIntegrationPublicId' responses: '204': description: Preflight accepted for the configured origin. '403': $ref: '#/components/responses/PublicPromptForbidden' '404': $ref: '#/components/responses/NotFound' post: operationId: executePromptIntegration summary: Execute a browser prompt integration description: | Sends schema-declared non-sensitive input to the operator-configured prompt and returns schema-valid JSON. This route has no API-key authentication; the exact configured Origin and a single-use Turnstile token are required. The integration URL and output schema remain stable across compatible administrative revisions. Usage is attributed to the account that owns the integration; this route does not accept or require a user_id for usage attribution. security: [] tags: [Prompt integrations] parameters: - $ref: '#/components/parameters/PromptIntegrationPublicId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PromptIntegrationExecuteRequest' responses: '200': description: Schema-valid JSON result. content: application/json: schema: $ref: '#/components/schemas/PromptIntegrationExecuteResponse' '400': $ref: '#/components/responses/PublicPromptValidation' '403': $ref: '#/components/responses/PublicPromptForbidden' '404': $ref: '#/components/responses/NotFound' '413': $ref: '#/components/responses/PublicPromptTooLarge' '422': $ref: '#/components/responses/PublicPromptValidation' '429': $ref: '#/components/responses/PublicPromptRateLimited' '502': $ref: '#/components/responses/PublicPromptModelFailure' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/agent-policy: get: operationId: getAgentPolicy summary: Read the account's current agent policy description: The bearer API key determines which account policy is returned. A missing policy is not an error in agent runtime behavior, but this management endpoint returns 404. tags: [Agent policy] responses: '200': description: The validated policy and its informational revision. content: application/json: schema: $ref: '#/components/schemas/AgentPolicyResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/PolicyNotFound' '405': $ref: '#/components/responses/PolicyMethodNotAllowed' '503': $ref: '#/components/responses/ServiceUnavailable' put: operationId: replaceAgentPolicy summary: Create or fully replace the account's agent policy description: >- Defines how the agent should behave, not how users should express themselves. Covers language, topic scope, natural interpretation, ambiguity handling, no invention, and privacy. Full replacement applied to the account selected by the bearer API key. Limited to 16 KiB. Topic matching is best-effort model behavior, not a deterministic content filter or safety boundary. tags: [Agent policy] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentPolicy' example: version: 1 instructions: >- Respondé en español rioplatense. Interpreta las consultas de forma natural: "listar consorcios", "mis consorcios", "qué consorcios tengo" significan lo mismo. Nunca inventes datos no devueltos por capabilities. Si es ambiguo, pedí aclaración. No expongas IDs internos. allowed_topics: - name: consorcios description: 'Gestión de consorcios: listado, unidades, gastos, pagos y liquidaciones.' - name: cuenta_corriente description: Saldos, deudas y movimientos de cuenta corriente. out_of_scope_response: No puedo ayudarte con esa consulta. responses: '200': description: Existing policy fully replaced. content: application/json: schema: $ref: '#/components/schemas/AgentPolicyResponse' '201': description: Policy created for the first time. content: application/json: schema: $ref: '#/components/schemas/AgentPolicyResponse' '400': description: Invalid JSON, invalid policy fields, or an empty policy. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalidJson: value: { error: INVALID_JSON, code: INVALID_JSON } invalidPolicy: value: { error: POLICY_INVALID, code: POLICY_INVALID } emptyPolicy: value: { error: POLICY_EMPTY, code: POLICY_EMPTY } '401': $ref: '#/components/responses/Unauthorized' '405': $ref: '#/components/responses/PolicyMethodNotAllowed' '413': $ref: '#/components/responses/PolicyRequestTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' delete: operationId: deleteAgentPolicy summary: Remove the account's agent policy description: Removes only the policy selected by the bearer API key. Subsequent runs use the platform defaults. tags: [Agent policy] responses: '204': description: Policy removed. '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/PolicyNotFound' '405': $ref: '#/components/responses/PolicyMethodNotAllowed' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/diagnostics/settings: get: operationId: getDiagnosticSettings summary: Read account diagnostic privacy settings description: The bearer API key determines which account's settings are returned. The default mode is metadata when no explicit setting exists. tags: [Diagnostics] responses: '200': description: Current account diagnostic settings. content: application/json: schema: $ref: '#/components/schemas/DiagnosticSettingsResponse' '401': $ref: '#/components/responses/Unauthorized' '405': $ref: '#/components/responses/DiagnosticsMethodNotAllowed' '503': $ref: '#/components/responses/ServiceUnavailable' put: operationId: replaceDiagnosticSettings summary: Set account diagnostic message mode description: Controls whether the account's capability-gap report is disabled, returns only metadata, or includes the original message text. This setting applies only to that account and does not change the core conversation storage policy or internal audit retention. tags: [Diagnostics] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DiagnosticSettings' example: diagnostic_message_mode: text responses: '200': description: Existing settings replaced. content: application/json: schema: $ref: '#/components/schemas/DiagnosticSettingsResponse' '201': description: Settings created for the first time. content: application/json: schema: $ref: '#/components/schemas/DiagnosticSettingsResponse' '400': description: Invalid JSON or diagnostic settings. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '405': $ref: '#/components/responses/DiagnosticsMethodNotAllowed' '413': $ref: '#/components/responses/DiagnosticsRequestTooLarge' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/diagnostics/capability-gaps: get: operationId: listCapabilityGaps summary: List unresolved capability diagnostics for the account description: Returns account-scoped coverage-gap and agent-dead-end events. Message text is included only when diagnostic_message_mode is text and remains subject to the platform's message retention policy. Results never include data from another account. tags: [Diagnostics] parameters: - name: limit in: query required: false schema: { type: integer, minimum: 1, maximum: 100, default: 50 } - name: before in: query required: false schema: { type: string, format: date-time } description: RFC3339 cursor timestamp ending in Z, used with before_id. - name: before_id in: query required: false schema: { type: string, minLength: 1, maxLength: 128 } description: Event ID cursor used with before. responses: '200': description: Account-scoped unresolved capability diagnostics in reverse chronological order. content: application/json: schema: $ref: '#/components/schemas/CapabilityGapResponse' '400': description: Invalid pagination parameters. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': $ref: '#/components/responses/Unauthorized' '405': $ref: '#/components/responses/DiagnosticsMethodNotAllowed' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/capability-credentials/{credentialRef}: delete: operationId: revokeCapabilityCredential summary: Revoke a stored host-endpoint credential tags: [Capabilities] parameters: - $ref: '#/components/parameters/CredentialRef' responses: '200': description: Credential revoked and no longer usable. content: application/json: schema: $ref: '#/components/schemas/CredentialResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' put: operationId: configureCapabilityCredential summary: Store or rotate a host-endpoint credential description: The secret is encrypted at rest and is never returned or included in a capability manifest. tags: [Capabilities] parameters: - $ref: '#/components/parameters/CredentialRef' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CredentialRequest' example: auth_type: bearer secret: endpoint-token-from-secret-manager responses: '201': description: Credential configured. The secret is not returned. content: application/json: schema: $ref: '#/components/schemas/CredentialResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '413': description: Request exceeds the credential size limit. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '503': description: Credential vault is unavailable. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /v1/capabilities: get: operationId: listCapabilities summary: List capabilities available to the API key tags: [Capabilities] responses: '200': description: Capability definitions scoped to the API key. content: application/json: schema: $ref: '#/components/schemas/CapabilityListResponse' '401': $ref: '#/components/responses/Unauthorized' post: operationId: createCapability summary: Validate and publish a single capability description: | Creates one capability from a single-definition payload. Use this instead of `/v1/capabilities/import` when adding a single capability without wrapping it in an array. Schema and credential validation happen before the capability is published atomically in `active` status. tags: [Capabilities] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CapabilityCreateRequest' example: domain: customers capability: name: lookup_customer description: Looks up one customer by email. when_to_use: Use when a customer asks for their account details. method: POST url: https://customer.example.com/v1/customers/lookup auth: type: bearer credential_ref: cred_customer_api safe_headers: accept: application/json content-type: application/json input_schema: type: object additionalProperties: false required: [email] properties: email: type: string maxLength: 254 output_schema: type: object additionalProperties: false required: [customer_id, email] properties: customer_id: type: string maxLength: 100 email: type: string maxLength: 254 business_rules: - The backend system is authoritative. examples: [] side_effect: read requires_approval: false timeout_ms: 5000 retry_count: 1 responses: '201': description: Capability validated and published as active. content: application/json: schema: $ref: '#/components/schemas/CapabilityLifecycleResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '409': $ref: '#/components/responses/Conflict' /v1/capabilities/import: post: operationId: importCapabilities summary: Validate and publish capabilities description: Schema and credential validation happen before all capabilities are published atomically in active status. Contract tests are independent diagnostics and do not change lifecycle state or revision. tags: [Capabilities] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CapabilityManifest' responses: '201': description: Capabilities validated and published as active. content: application/json: schema: $ref: '#/components/schemas/CapabilityImportResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '409': $ref: '#/components/responses/Conflict' /v1/capabilities/{capabilityId}: get: operationId: getCapability summary: Get one capability tags: [Capabilities] parameters: - $ref: '#/components/parameters/CapabilityId' responses: '200': description: Capability definition and revision history. content: application/json: schema: $ref: '#/components/schemas/CapabilityDetailResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' patch: operationId: reviseCapability summary: Validate and publish a replacement revision description: If-Match must contain the current numeric revision. The complete replacement is schema- and credential-validated before an atomic write. The current active or disabled state is preserved; the immutable capability ID does not change. tags: [Capabilities] parameters: - $ref: '#/components/parameters/CapabilityId' - $ref: '#/components/parameters/IfMatch' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CapabilityPatchRequest' responses: '200': description: Replacement revision validated and published. content: application/json: schema: $ref: '#/components/schemas/CapabilityLifecycleResponse' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /v1/capabilities/{capabilityId}/test: post: operationId: testCapability summary: Run a contract test for a capability revision description: Calls the registered active read-only endpoint at the selected revision with the supplied bounded arguments. The result is stored as an independent contract-test record and does not change lifecycle state or revision. tags: [Capabilities] parameters: - $ref: '#/components/parameters/CapabilityId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CapabilityTestRequest' example: arguments: name: Juan Pérez responses: '200': description: Contract test result. content: application/json: schema: $ref: '#/components/schemas/CapabilityTestResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /v1/capabilities/{capabilityId}/enable: post: operationId: enableCapability summary: Explicitly enable a capability description: Requires the current If-Match revision. Enabling rechecks the credential reference and creates a new active revision. It never happens implicitly through PATCH. tags: [Capabilities] parameters: - $ref: '#/components/parameters/CapabilityId' - $ref: '#/components/parameters/IfMatch' responses: '200': description: Capability is active. content: application/json: schema: $ref: '#/components/schemas/CapabilityLifecycleResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' /v1/capabilities/{capabilityId}/disable: post: operationId: disableCapability summary: Disable a capability tags: [Capabilities] parameters: - $ref: '#/components/parameters/CapabilityId' - $ref: '#/components/parameters/IfMatch' responses: '200': description: Capability is disabled. content: application/json: schema: $ref: '#/components/schemas/CapabilityLifecycleResponse' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' components: securitySchemes: bearerAuth: type: http scheme: bearer description: API key issued out of band by the Forgium administrator. parameters: PromptIntegrationPublicId: name: publicId in: path required: true schema: type: string pattern: '^pi_public_[A-Za-z0-9_-]{16,80}$' minLength: 27 maxLength: 90 CredentialRef: name: credentialRef in: path required: true schema: type: string pattern: '^cred_[A-Za-z0-9_-]{3,120}$' minLength: 7 maxLength: 128 CapabilityId: name: capabilityId in: path required: true schema: type: string pattern: '^cap_[a-z0-9]+$' minLength: 8 maxLength: 80 IdempotencyKey: name: Idempotency-Key in: header required: true schema: type: string minLength: 1 maxLength: 200 IfMatch: name: If-Match in: header required: true schema: type: string minLength: 1 maxLength: 80 schemas: PromptIntegrationExecuteRequest: type: object additionalProperties: false required: [input, turnstile_token] properties: input: type: object additionalProperties: true description: Object validated against the integration's operator-configured input schema. turnstile_token: type: string minLength: 1 maxLength: 2048 description: >- Single-use token returned by the Turnstile widget. The widget must be rendered with the integration's turnstile_action value — the server rejects tokens whose action does not match. PromptIntegrationExecuteResponse: type: object additionalProperties: false required: [data] properties: data: type: object additionalProperties: true description: Object validated against the integration's operator-configured output schema. HealthResponse: type: object additionalProperties: false required: [status, service] properties: status: type: string enum: [ok] maxLength: 16 service: type: string maxLength: 64 AgentPolicy: type: object additionalProperties: false required: [version] description: At least one of instructions or allowed_topics is required. out_of_scope_response is valid only with allowed_topics. Omitted fields keep their platform defaults. anyOf: - required: [instructions] - required: [allowed_topics] dependentRequired: out_of_scope_response: [allowed_topics] properties: version: type: integer const: 1 instructions: type: string minLength: 1 maxLength: 2000 pattern: '\S' description: Optional account-authored style or behavior instructions. allowed_topics: type: array minItems: 1 maxItems: 20 items: $ref: '#/components/schemas/AgentPolicyTopic' description: Optional allowlist of topics. Matching is best-effort model behavior. out_of_scope_response: type: string minLength: 1 maxLength: 500 pattern: '\S' description: Optional fallback for requests outside allowed_topics. Defaults to No puedo ayudarte con esa consulta. AgentPolicyTopic: type: object additionalProperties: false required: [name, description] properties: name: type: string minLength: 1 maxLength: 80 pattern: '\S' description: Unique topic label, compared case-insensitively after trimming. description: type: string minLength: 1 maxLength: 500 pattern: '\S' description: Boundary description shown to the model. AgentPolicyResponse: type: object additionalProperties: false required: [revision, updated_at, policy] properties: revision: type: integer minimum: 1 updated_at: type: string format: date-time maxLength: 64 policy: $ref: '#/components/schemas/AgentPolicy' DiagnosticSettings: type: object additionalProperties: false required: [diagnostic_message_mode] description: Account-owned privacy setting for the capability-gap report. Metadata is the safe default; text is opt-in. properties: diagnostic_message_mode: type: string enum: [metadata, text, disabled] description: Whether capability-gap diagnostics are disabled, return only metadata, or also return the original message text. DiagnosticSettingsResponse: type: object additionalProperties: false required: [revision, updated_at, settings] properties: revision: type: integer minimum: 0 updated_at: type: [string, 'null'] format: date-time settings: $ref: '#/components/schemas/DiagnosticSettings' CapabilityGapResponse: type: object additionalProperties: false required: [message_mode, has_more, events] properties: message_mode: type: string enum: [metadata, text, disabled] has_more: type: boolean events: type: array maxItems: 100 items: $ref: '#/components/schemas/CapabilityGapEvent' CapabilityGapEvent: type: object additionalProperties: false required: [id, agent_run_id, category, capability, revision, created_at] properties: id: type: string description: Opaque diagnostic event ID owned by the authenticated account. agent_run_id: type: string description: Opaque agent-run correlation ID. message_id: type: string description: Opaque user-message correlation ID, when available. category: type: string enum: [coverage_gap, agent_dead_end] capability: type: [string, 'null'] revision: type: [integer, 'null'] available_capabilities: type: array maxItems: 20 description: Existing eligible capability names considered during the run, when available. items: type: string minLength: 1 maxLength: 128 conversation_id: type: string description: Present only when message text mode is enabled and the source message is retained. message: type: string maxLength: 10000 description: Original user message, returned only when message text mode is enabled and with sensitive token-shaped values redacted. redacted: type: boolean description: Whether sensitive token-shaped content was replaced before returning the message. truncated: type: boolean description: Whether the returned message was bounded to the maximum diagnostic message length. created_at: type: string format: date-time HttpChannelRequest: type: object additionalProperties: false required: [message] properties: conversation_id: type: string minLength: 1 maxLength: 128 user_id: type: string minLength: 1 maxLength: 200 description: >- Opaque external user identifier recommended for per-user usage attribution. Optional during client migration; if omitted, usage remains attributed to the account without an individual user. No mandatory migration date is announced. message: $ref: '#/components/schemas/TextMessageRequest' metadata: type: object additionalProperties: type: string maxLength: 500 maxProperties: 30 TextMessageRequest: type: object additionalProperties: false required: [type, body] properties: type: type: string enum: [text] maxLength: 16 body: type: string minLength: 1 maxLength: 10000 HttpChannelMultipartRequest: type: object additionalProperties: false required: [payload, image] properties: payload: $ref: '#/components/schemas/HttpChannelMultipartPayload' image: type: string format: binary description: One JPEG, PNG, or WebP image, at most 4 MiB. The binary is processed in memory and not persisted. HttpChannelMultipartPayload: type: object additionalProperties: false required: [message] description: JSON payload part. It has the same fields as HttpChannelRequest; its message body may be empty because the image supplies the content. properties: conversation_id: type: string minLength: 1 maxLength: 128 user_id: type: string minLength: 1 maxLength: 200 message: type: object additionalProperties: false required: [type, body] properties: type: type: string enum: [text] body: type: string minLength: 0 maxLength: 10000 metadata: type: object additionalProperties: type: string maxLength: 500 maxProperties: 30 HttpChannelResponse: type: object additionalProperties: false required: [conversation_id, message] properties: conversation_id: type: string minLength: 1 maxLength: 128 message: $ref: '#/components/schemas/AssistantMessage' interaction: $ref: '#/components/schemas/HostInteraction' capability_trace: $ref: '#/components/schemas/CapabilityTrace' benchmark_observation: $ref: '#/components/schemas/BenchmarkObservation' CapabilityTrace: type: object additionalProperties: false required: [version, run_id, message_id, events] properties: version: type: string enum: [v1] run_id: type: string minLength: 1 maxLength: 128 description: Opaque identifier for the current agent run. message_id: type: string minLength: 1 maxLength: 128 description: Opaque identifier for the user message that initiated the run. events: type: array maxItems: 8 items: $ref: '#/components/schemas/CapabilityTraceEvent' timing: type: object additionalProperties: false description: | Internal timing breakdown for the agent run. All values are non-negative integers in milliseconds unless noted. Omitted when the worker does not return timing data or when the sanitizer rejects unknown keys. properties: preparation_ms: type: integer minimum: 0 maximum: 300000 description: Time spent preparing the run context. selection_inference_ms: type: integer minimum: 0 maximum: 300000 description: Inference time for capability selection. selection_inference_attempts: type: integer minimum: 1 maximum: 10 description: Number of selection inference attempts. selection_retry_delay_ms: type: integer minimum: 0 maximum: 300000 description: Cumulative delay between selection retries. execution_ms: type: integer minimum: 0 maximum: 300000 description: Time spent executing the selected capability. executor_attempts: type: integer minimum: 1 maximum: 10 description: Number of executor attempts. grounded_inference_ms: type: integer minimum: 0 maximum: 300000 description: Inference time for grounded response generation. e2e_processing_ms: type: integer minimum: 0 maximum: 300000 description: Total wall-clock time from request receipt to response. CapabilityTraceEvent: type: object additionalProperties: false required: [ordinal, stage, status] properties: ordinal: type: integer minimum: 1 stage: type: string enum: [selection, argument_validation, execution, response_generation, run] status: type: string description: Sanitized current-run state; this is not model reasoning. enum: [not_selected, selected, arguments_invalid, execution_failed, empty_result, result_received, response_generated, unresolved, runtime_failed] reason_code: type: string description: Bounded technical category. `safe_fallback` is terminal, not causal; inspect the preceding event. enum: [no_tool_call, no_active_capabilities, policy_out_of_scope, schema_required_missing, schema_invalid, input_invalid, capability_not_active, approval_required, upstream_timeout, upstream_unavailable, upstream_http_error, response_invalid, business_outcome_failed, structurally_empty, grounded, answer_only, safe_fallback, run_deadline_exceeded, coverage_gap, out_of_scope, agent_dead_end, selection_inference_failed, grounded_inference_failed] capability: type: string minLength: 1 maxLength: 128 revision: type: integer minimum: 1 interaction_type: type: string enum: [confirm_operation, select_option] description: Bounded host interaction discriminator; opaque references are never included in traces. BenchmarkObservation: type: object additionalProperties: false required: [capability, argument_category, fixture_result, status, model, correlation_id] properties: capability: type: [string, 'null'] maxLength: 128 argument_category: type: string enum: [known, unknown, missing] fixture_result: type: string enum: [non_empty, empty, error, ambiguous] status: type: integer enum: [200] model: type: string minLength: 1 maxLength: 256 correlation_id: type: string minLength: 1 maxLength: 128 HostInteractionPreparationResult: description: >- Exact result returned by a capability endpoint whose result_handling is host_interaction. Forgium sanitizes it against the declared canonical output schema and then enforces this discriminated union. This call is a just-in-time host preflight after the user expresses an operation intent. The host resolves resources and current eligibility during the call and must revalidate before any later mutation. oneOf: - type: object additionalProperties: false required: [status, confirmation] properties: status: type: string enum: [confirmation_required] confirmation: type: object additionalProperties: false required: [operation, operation_ref, expires_at, preview] properties: operation: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' maxLength: 64 operation_ref: type: string minLength: 1 maxLength: 512 expires_at: type: string format: date-time maxLength: 35 preview: $ref: '#/components/schemas/HostInteractionPreview' - type: object additionalProperties: false required: [status, selection] properties: status: type: string enum: [selection_required] selection: type: object additionalProperties: false required: [operation, selection_ref, expires_at, options] properties: operation: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' maxLength: 64 selection_ref: type: string minLength: 1 maxLength: 512 expires_at: type: string format: date-time maxLength: 35 options: type: array minItems: 2 maxItems: 8 items: $ref: '#/components/schemas/HostInteractionOption' - type: object description: >- No operation is currently eligible. This covers both no matching resource and matching resources whose state makes the requested operation ineligible; it does not assert that the resource is absent and does not distinguish those causes. additionalProperties: false required: [status] properties: status: type: string enum: [not_found] HostInteraction: oneOf: - type: object additionalProperties: false required: [interaction_id, owner, type, operation, operation_ref, expires_at, preview] properties: interaction_id: type: string minLength: 1 maxLength: 128 owner: type: string enum: [host] type: type: string enum: [confirm_operation] operation: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' minLength: 2 maxLength: 64 operation_ref: type: string minLength: 1 maxLength: 512 expires_at: type: string format: date-time maxLength: 64 preview: $ref: '#/components/schemas/HostInteractionPreview' - type: object additionalProperties: false required: [interaction_id, owner, type, operation, selection_ref, expires_at, options] properties: interaction_id: type: string minLength: 1 maxLength: 128 owner: type: string enum: [host] type: type: string enum: [select_option] operation: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' minLength: 2 maxLength: 64 selection_ref: type: string minLength: 1 maxLength: 512 expires_at: type: string format: date-time maxLength: 64 options: type: array minItems: 2 maxItems: 8 items: $ref: '#/components/schemas/HostInteractionOption' HostInteractionPreview: type: object additionalProperties: false required: [title, fields] properties: title: type: string minLength: 1 maxLength: 120 fields: type: array minItems: 1 maxItems: 8 items: $ref: '#/components/schemas/HostInteractionField' HostInteractionField: type: object additionalProperties: false required: [label, value] properties: label: type: string minLength: 1 maxLength: 80 value: type: string minLength: 1 maxLength: 500 HostInteractionOption: type: object additionalProperties: false required: [option_ref, label] properties: option_ref: type: string minLength: 1 maxLength: 512 label: type: string minLength: 1 maxLength: 200 HostOperationResultRequest: type: object additionalProperties: false required: [interaction_id, operation, status] properties: interaction_id: type: string minLength: 1 maxLength: 128 operation: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' minLength: 2 maxLength: 64 status: type: string enum: [succeeded, resource_changed, rejected, failed] HostOperationResultResponse: allOf: - $ref: '#/components/schemas/HostOperationResultRequest' AssistantMessage: type: object additionalProperties: false required: [id, role, type, body] properties: id: type: string minLength: 1 maxLength: 128 role: type: string enum: [assistant] maxLength: 16 type: type: string enum: [text] maxLength: 16 body: type: string maxLength: 20000 CredentialRequest: type: object additionalProperties: false required: [auth_type, secret] properties: auth_type: type: string enum: [bearer, api_key, basic] maxLength: 16 header_name: type: string enum: [x-api-key, x-api-token] maxLength: 32 secret: type: string minLength: 1 maxLength: 4096 CredentialResponse: type: object additionalProperties: false required: [credential_ref, status] properties: credential_ref: type: string maxLength: 128 status: type: string enum: [configured, revoked] maxLength: 32 JwksResponse: type: object additionalProperties: false required: [keys] properties: keys: type: array minItems: 1 maxItems: 10 items: $ref: '#/components/schemas/PublicSigningKey' PublicSigningKey: type: object additionalProperties: false required: [kty, crv, x, kid, use, alg] properties: kty: type: string enum: [OKP] crv: type: string enum: [Ed25519] x: type: string maxLength: 128 kid: type: string minLength: 1 maxLength: 128 use: type: string enum: [sig] alg: type: string enum: [EdDSA] CapabilityManifest: type: object additionalProperties: false required: [domain, capabilities] properties: domain: type: string pattern: '^[a-z][a-z0-9_-]{1,63}$' minLength: 2 maxLength: 64 capabilities: type: array minItems: 1 maxItems: 50 items: $ref: '#/components/schemas/CapabilityDefinition' CapabilityCreateRequest: type: object additionalProperties: false required: [domain, capability] description: Single-capability creation payload. Use instead of CapabilityManifest when adding one capability without an array wrapper. properties: domain: type: string pattern: '^[a-z][a-z0-9_-]{1,63}$' minLength: 2 maxLength: 64 capability: $ref: '#/components/schemas/CapabilityDefinition' CapabilityDefinition: type: object additionalProperties: false required: [name, description, when_to_use, method, url, auth, safe_headers, input_schema, output_schema, side_effect, requires_approval, timeout_ms, retry_count] properties: name: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' minLength: 2 maxLength: 64 description: type: string minLength: 1 maxLength: 1000 when_to_use: type: string minLength: 1 maxLength: 1000 method: type: string enum: [GET, POST, PUT, PATCH, DELETE] maxLength: 6 url: type: string format: uri pattern: '^https://' maxLength: 2048 allowed_host: type: string maxLength: 253 auth: $ref: '#/components/schemas/CapabilityAuth' safe_headers: type: object additionalProperties: false properties: accept: type: string maxLength: 100 content-type: type: string enum: [application/json] maxLength: 100 input_schema: $ref: '#/components/schemas/JsonSchemaDocument' description: >- Bounded JSON Schema for capability arguments. Supported keywords: type, properties, required, additionalProperties, items, maxItems, minLength, maxLength, enum, const, description, anyOf, oneOf, not, and pattern. pattern is accepted only for bounded string schemas and uses ECMAScript regular-expression syntax. Use oneOf to require exactly one alternative. allOf, $ref, numeric bounds, minItems, format, and every other keyword are rejected at import. output_schema: $ref: '#/components/schemas/JsonSchemaDocument' description: >- Bounded JSON Schema for capability results. Supported keywords: type, properties, required, additionalProperties, items, maxItems, minLength, maxLength, enum, const, and description. Composition keywords (anyOf, oneOf, not, allOf) and every other keyword are rejected so the executor can safely sanitize one explicit shape. A host_interaction capability must declare the complete canonical status/confirmation/selection schema published in capability-handoff; Forgium then validates the sanitized result against HostInteractionPreparationResult as well. image_interpretation: $ref: '#/components/schemas/ImageInterpretation' description: >- Optional image-assisted extraction contract. It provides partial values for input_schema fields when a multipart image is sent; input_schema.required remains the source of truth for missing fields. context_bindings: type: object maxProperties: 16 patternProperties: '^[a-z][a-z0-9_]{0,127}$': $ref: '#/components/schemas/CapabilityContextBinding' additionalProperties: false description: >- Optional declarative handoff metadata for validated conversational references. Each key must be a string property in input_schema; Forgium uses the accepted reference fields and producer capability names only as a bounded provenance check. It never grants authorization or bypasses executor validation. business_rules: type: array description: >- Bounded routing guidance. Explicit `Do not use for ...` clauses are enforced as fail-closed exclusions before executor I/O; they cannot authorize or expand a capability. maxItems: 50 items: type: string maxLength: 1000 examples: type: array maxItems: 20 items: type: object additionalProperties: true side_effect: type: string enum: [read] description: 'Contract 5 capabilities are read-only. Use result_handling: host_interaction for bounded preparation of host-owned operations.' requires_approval: type: boolean const: false result_handling: type: string enum: [model_response, host_interaction] description: >- `host_interaction` is a read-only, non-retrying, uncached preparation capability. Its output schema must declare the closed canonical status object with complete confirmation and selection properties. The endpoint result is additionally validated against the discriminated confirmation/selection/not-found union. timeout_ms: type: integer minimum: 1000 maximum: 10000 retry_count: type: integer minimum: 0 maximum: 1 cache_ttl_seconds: type: integer minimum: 0 maximum: 86400 CapabilityAuth: type: object additionalProperties: false required: [type] properties: type: type: string enum: [bearer, api_key, basic, none] maxLength: 16 credential_ref: type: string pattern: '^cred_[A-Za-z0-9_-]{3,120}$' maxLength: 128 header_name: type: string enum: [x-api-key, x-api-token] maxLength: 32 CapabilityContextBinding: type: object additionalProperties: false required: [entity, accepted_reference_fields, producer_capabilities] properties: entity: type: string pattern: '^[a-z][a-z0-9_-]{1,63}$' maxLength: 64 accepted_reference_fields: type: array minItems: 1 maxItems: 8 uniqueItems: true items: type: string pattern: '(^|_)(name|nombre|title|titulo|label|etiqueta)$' maxLength: 128 producer_capabilities: type: array minItems: 1 maxItems: 8 uniqueItems: true items: type: string pattern: '^[a-z][a-z0-9_]{1,63}$' maxLength: 64 JsonSchemaDocument: type: object description: Bounded JSON Schema document used to validate capability arguments or results; see the field-specific descriptions for the exact keyword matrix. additionalProperties: true ImageInterpretation: type: object additionalProperties: false required: [document_kinds, extraction_schema] properties: document_kinds: type: array minItems: 1 maxItems: 3 uniqueItems: true description: Supported document categories for image-assisted capability selection. items: type: string enum: [bank_transfer_receipt, invoice, receipt] extraction_schema: $ref: '#/components/schemas/JsonSchemaDocument' description: >- Partial object schema for image extraction. It must use additionalProperties false, must not declare required fields, and every declared field must also exist compatibly in input_schema. CapabilityImportResponse: type: object additionalProperties: false required: [capabilities] properties: capabilities: type: array maxItems: 50 items: $ref: '#/components/schemas/CapabilityLifecycleResponse' CapabilityListResponse: type: object additionalProperties: false required: [capabilities] properties: capabilities: type: array maxItems: 1000 items: $ref: '#/components/schemas/CapabilitySummary' CapabilitySummary: type: object unevaluatedProperties: false required: [id, name, description, when_to_use, input_schema, output_schema, business_rules, examples, side_effect, requires_approval, revision, lifecycle_status] properties: id: type: string maxLength: 80 name: type: string maxLength: 64 description: type: string maxLength: 1000 when_to_use: type: string maxLength: 1000 input_schema: $ref: '#/components/schemas/JsonSchemaDocument' output_schema: $ref: '#/components/schemas/JsonSchemaDocument' image_interpretation: $ref: '#/components/schemas/ImageInterpretation' description: Optional image-assisted extraction contract published with this capability. context_bindings: type: object maxProperties: 16 patternProperties: '^[a-z][a-z0-9_]{0,127}$': $ref: '#/components/schemas/CapabilityContextBinding' additionalProperties: false description: Optional provenance metadata for validated conversational reference handoff. business_rules: type: array description: >- Bounded routing guidance. Explicit `Do not use for ...` clauses are enforced as fail-closed exclusions before executor I/O; they cannot authorize or expand a capability. maxItems: 50 items: type: string maxLength: 1000 examples: type: array maxItems: 20 items: type: object additionalProperties: true side_effect: type: string enum: [read] description: Contract 5 capabilities are read-only. requires_approval: type: boolean const: false result_handling: type: string enum: [model_response, host_interaction] description: The deterministic result handling mode persisted for this capability. revision: type: integer minimum: 1 lifecycle_status: type: string enum: [active, disabled] maxLength: 16 credential_configured: type: boolean updated_at: type: string format: date-time maxLength: 64 CapabilityDetailResponse: allOf: - $ref: '#/components/schemas/CapabilitySummary' - type: object unevaluatedProperties: false required: [method, url, revisions] properties: method: type: string maxLength: 6 url: type: string format: uri maxLength: 2048 revisions: type: array maxItems: 100 items: $ref: '#/components/schemas/CapabilityRevision' CapabilityRevision: type: object additionalProperties: false required: [revision, change_type, actor_type, actor_id, created_at] properties: revision: type: integer minimum: 1 change_type: type: string maxLength: 32 actor_type: type: string maxLength: 32 actor_id: type: string maxLength: 128 created_at: type: string format: date-time maxLength: 64 CapabilityPatchRequest: type: object additionalProperties: false required: [capability] properties: domain: type: string maxLength: 64 capability: $ref: '#/components/schemas/CapabilityDefinition' CapabilityTestRequest: type: object additionalProperties: false required: [arguments] properties: arguments: type: object additionalProperties: true description: >- Arguments sent to the real active endpoint at the current revision. Forgium signs a deterministic test scope with subject_id forgium-contract-test and conversation_id contract-test:{capabilityId}. Contract tests have no declarative expectation block; any valid output branch passes. CapabilityTestResponse: type: object additionalProperties: false required: [status, capability_id, revision] properties: status: type: string enum: [passed, failed] maxLength: 16 capability_id: type: string maxLength: 80 revision: type: integer minimum: 1 error_code: type: string maxLength: 100 CapabilityLifecycleResponse: type: object additionalProperties: false required: [id, lifecycle_status, revision] properties: id: type: string maxLength: 80 lifecycle_status: type: string enum: [active, disabled] maxLength: 16 revision: type: integer minimum: 1 PublicPromptErrorResponse: type: object additionalProperties: false required: [error] properties: error: type: object additionalProperties: false required: [code, message] properties: code: type: string maxLength: 100 message: type: string maxLength: 100 ErrorResponse: type: object additionalProperties: false required: [error, code] properties: error: type: string maxLength: 500 code: type: string maxLength: 100 HttpErrorResponse: type: object additionalProperties: false required: [error] properties: error: type: object additionalProperties: false required: [code, message] properties: code: type: string maxLength: 100 message: type: string maxLength: 500 details: type: object additionalProperties: true MetaWebhookResponse: type: object additionalProperties: false required: [status, accepted, duplicates, ignored] properties: status: type: string enum: [accepted, ignored] accepted: type: integer minimum: 0 duplicates: type: integer minimum: 0 ignored: type: integer minimum: 0 responses: PublicPromptValidation: description: The JSON body or schema-declared input is invalid. content: application/json: schema: $ref: '#/components/schemas/PublicPromptErrorResponse' PublicPromptForbidden: description: The browser origin or Turnstile verification is not accepted. content: application/json: schema: $ref: '#/components/schemas/PublicPromptErrorResponse' PublicPromptTooLarge: description: The request body exceeds the configured boundary. content: application/json: schema: $ref: '#/components/schemas/PublicPromptErrorResponse' PublicPromptRateLimited: description: The integration/IP burst limit or daily quota was exceeded. headers: Retry-After: schema: type: string content: application/json: schema: $ref: '#/components/schemas/PublicPromptErrorResponse' PublicPromptModelFailure: description: Workers AI was unavailable or returned schema-invalid JSON. content: application/json: schema: $ref: '#/components/schemas/PublicPromptErrorResponse' Unauthorized: description: API key is missing, invalid, expired, or revoked. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: Invalid API key code: UNAUTHORIZED ValidationError: description: Request body or parameter is invalid. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: VALIDATION_ERROR code: VALIDATION_ERROR HttpMethodNotAllowed: description: HTTP method is not supported for this route. headers: Allow: schema: type: string maxLength: 100 content: application/json: schema: $ref: '#/components/schemas/HttpErrorResponse' HttpDuplicateEvent: description: The message event was already processed. content: application/json: schema: $ref: '#/components/schemas/HttpErrorResponse' HttpValidationError: description: The HTTP channel request is invalid. content: application/json: schema: $ref: '#/components/schemas/HttpErrorResponse' HttpRequestTooLarge: description: The HTTP channel body exceeds the boundary limit. content: application/json: schema: $ref: '#/components/schemas/HttpErrorResponse' HttpRuntimeError: description: The agent runtime could not produce a response. content: application/json: schema: $ref: '#/components/schemas/HttpErrorResponse' MetaWebhookForbidden: description: The Meta webhook verification token is invalid. content: text/plain: schema: { type: string } MetaWebhookBadRequest: description: The Meta webhook body is invalid JSON or has an unsupported shape. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' MetaWebhookUnauthorized: description: The Meta webhook signature is missing or invalid. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' MetaWebhookPhoneNotRegistered: description: The phone number in the event is not registered to a Forgium account. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' MetaWebhookTooLarge: description: The Meta webhook body exceeds the 1 MiB boundary. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Capability does not exist for this API key. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' PolicyNotFound: description: No agent policy exists for this API key. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: POLICY_NOT_FOUND code: POLICY_NOT_FOUND PolicyMethodNotAllowed: description: The agent policy endpoint accepts only GET, PUT, and DELETE. headers: Allow: schema: type: string enum: [GET, PUT, DELETE] content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: METHOD_NOT_ALLOWED code: METHOD_NOT_ALLOWED PolicyRequestTooLarge: description: The agent policy JSON body exceeds 16 KiB. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: REQUEST_TOO_LARGE code: REQUEST_TOO_LARGE DiagnosticsMethodNotAllowed: description: The diagnostics settings endpoint accepts only GET and PUT; the capability-gap endpoint accepts only GET. headers: Allow: schema: type: string content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: METHOD_NOT_ALLOWED code: METHOD_NOT_ALLOWED DiagnosticsRequestTooLarge: description: The diagnostic settings JSON body exceeds 1 KiB. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: REQUEST_TOO_LARGE code: REQUEST_TOO_LARGE Conflict: description: The requested operation conflicts with the current capability state. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Gone: description: The host interaction expired before the report was accepted. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' RequestTooLarge: description: The request body exceeds the bounded operation-result limit. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' UpstreamFailure: description: The capability endpoint failed; no automatic mutation retry is performed. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' ServiceUnavailable: description: The requested service is temporarily unavailable. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' --- END docs/integration/api.openapi.yaml --- --- BEGIN docs/integration/agent-consumer-pack.md --- # Forgium Agent — integration package This exported package is the public contract for an external developer or AI agent integrating an application with Forgium Agent. > **Compatibility URL:** the canonical agent entrypoint is > [Use Forgium Agent with an agent](use-with-an-agent.md). This page remains > available so existing public links do not break. ## Start here Read the documents in this order: 1. [Overview](overview.md) — what Forgium Agent does and which integration path to choose. 2. [Getting started](getting-started.md) — receive the access bundle and send the first HTTP message. 3. [Environments](environments.md) — isolate development, staging, and production. 4. Choose the task-specific guide: - [Business capabilities](capability-handoff.md) for live data or host-owned operation preparation. - [Capability-gap diagnostics](diagnostics.md) to identify unsupported account requests. - [OpenAPI](api.openapi.yaml) for the complete Public API contract. The Skill source is not currently distributed as an installable artifact. Use the universal agent entrypoint above instead. ## Authority boundary The Forgium administrator provides this access bundle: ```bash FORGIUM_AGENT_BASE_URL=https:// FORGIUM_AGENT_API_KEY=mg_xxx ``` Use the API key with the Public API (`/v1/*`) to send messages and manage business capabilities. If either value is missing, ask the administrator. ## Completion paths | Goal | Completion condition | |---|---| | Send a message from a host application | A request to `POST /v1/channels/http/messages` returns an assistant message. | | Give the agent live business data or prepare a host operation | A scoped read-only host endpoint is validated and imported as active, contract-tested, and verified through chat. | A generic response to a domain query usually means the required business capability is absent or inactive. Follow the complete lifecycle in [capability-handoff.md](capability-handoff.md); do not substitute chat middleware, a proxy workaround, a placeholder URL, or an unauthenticated host endpoint. For AI-agent implementations, use `input_schema` as the source of truth for mandatory and optional arguments. Do not invent missing values. Contract 5 capabilities are read-only. A preparation capability can return a host-owned `interaction`; route by its type, keep opaque references in the host flow, and perform any confirmation, authorization, concurrency, idempotency, and mutation in the host application. Preparation is a just-in-time preflight after the user expresses an operation intent, not a requirement to predict future actions. The host checks current eligibility during preparation and again before mutation. `not_found` means no operation is currently eligible; it covers both no matching resource and a matching resource whose state does not permit the requested operation. --- END docs/integration/agent-consumer-pack.md --- --- BEGIN docs/skill/micro-agent-integrator/SKILL.md --- --- name: micro-agent-integrator description: >- Optional workflow for integrating an external application with Forgium Agent over the public HTTP channel and host-application capabilities. version: 1.2.0 --- # Forgium Agent Integrator This source is public for runtimes that already support a local `SKILL.md`; it is not currently distributed through an installable package or registry. The canonical contract remains the public documentation. ## Read first `` means the root of the exported public documentation package. Read in this order: 1. `/integration/use-with-an-agent.md` 2. `/integration/overview.md` 3. `/integration/environments.md` 4. `/integration/request-access.md` when access is missing 5. `/integration/getting-started.md` for HTTP 6. `/integration/capability-handoff.md` for capabilities 7. `/integration/agent-policy.md` for optional policy configuration 8. `/integration/api.openapi.yaml` for exact schemas ## Workflow 1. Determine whether the task is connectivity-only or code integration. 2. For connectivity-only, do not inspect or modify an unrelated repository: use the access bundle and the HTTP quickstart to send one message. 3. For code integration, inspect the host repository and identify the requested behavior. Choose the minimum route: HTTP for a synchronous agent response, or capabilities for approved live host data/actions. Capabilities are optional. 4. Confirm `FORGIUM_AGENT_BASE_URL` and `FORGIUM_AGENT_API_KEY` only when a real API call or deployment is required. If either is missing, request the environment-specific bundle and continue with local work or mocks. 5. Implement only the selected route using the canonical guide and OpenAPI contract. Do not invent a data source, tunnel, framework, or deployment. 6. Run local tests and the applicable authenticated smoke test. 7. For capabilities, follow the canonical lifecycle: protect and expose the endpoint, provision its scoped credential, create a secret-free manifest, import it as a validated active revision, contract-test, and verify through chat. Use explicit `enable` or `disable` transitions with `If-Match` when managing published state. Never use a placeholder URL or unauthenticated endpoint. 8. Report changes, verification evidence, and every actionable blocker. Do not claim a capability is active without successful import validation and chat verification. ## Security boundary Use only the Public API with the supplied bundle. Never request Admin API credentials, platform secrets, internal bindings, or secrets from source control. Keep API keys and endpoint tokens out of manifests, prompts, logs, screenshots, and persisted agent context. --- END docs/skill/micro-agent-integrator/SKILL.md --- --- BEGIN docs/integration/prompt-integrations-example-foda.md --- # Prompt integration example: FODA matrix End-to-end example of a prompt integration. Shows the manifest, the bundle, and how the frontend consumes the endpoint. ## Use case An operator creates a prompt integration that generates a FODA (SWOT) matrix from a business description. The integration accepts one text field and returns four arrays. --- ## Manifest ```json { "version": 1, "name": "foda_form", "origin": "https://tu-empresa.com", "data_classification": "non_sensitive", "prompt": "Analizá el contexto de negocio y generá una matriz FODA con 4-8 items por categoría. Cada item debe ser concreto y basado en el input.", "input_schema": { "type": "object", "additionalProperties": false, "required": ["business_context"], "properties": { "business_context": { "type": "string", "minLength": 20, "maxLength": 4000 } } }, "output_schema": { "type": "object", "additionalProperties": false, "required": ["strengths", "weaknesses", "opportunities", "threats"], "properties": { "strengths": { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } }, "weaknesses": { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } }, "opportunities": { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } }, "threats": { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } } } } } ``` ### Supported schema subset | Feature | Supported | |---|---| | Root `type: "object"` | ✅ | | `additionalProperties: false` | ✅ | | `required`, `properties` | ✅ | | `string` with `minLength` / `maxLength` | ✅ | | `array` with `minItems` / `maxItems` | ✅ | | `integer` / `number` with bounds | ✅ | | `boolean` | ✅ | | Nested objects | ✅ | | `enum` (strings) | ✅ | | `$ref`, `oneOf`, `anyOf`, `allOf` | ❌ | --- ## Bundle The Admin API returns this after creating the manifest: ```json { "id": "pi_a1b2c3d4e5f6", "revision": 1, "status": "active", "endpoint": "https://api.forgium.dev/v1/prompt-integrations/pi_pub_x7y8z9w0/execute", "origin": "https://tu-empresa.com", "turnstile_sitekey": "0x4AAAAAAAxxxxxxxxxxxxxx", "turnstile_action": "forgium_prompt_execute", "input_schema": { "type": "object", "additionalProperties": false, "required": ["business_context"], "properties": { "business_context": { "type": "string", "minLength": 20, "maxLength": 4000 } } } } ``` ### What is NOT in the bundle | Field | Why | |---|---| | `prompt` | Runs server-side only | | `output_schema` | Validated server-side; frontend receives `data` | | `turnstile_secret` | Never leaves the Worker | | Admin paths | Not exposed in public docs | --- ## Frontend integration ### 1. Load Turnstile ```html ``` Render the widget with **both** `sitekey` and `action`: ```js turnstile.render("#container", { sitekey: "0x4AAAAAAAxxxxxxxxxxxxxx", action: "forgium_prompt_execute", // mandatory callback: (token) => { /* store token */ }, "expired-callback": () => { /* reset widget */ }, }); ``` The `action` value comes from the bundle. It is not a secret. ### 2. Submit ```js const resp = await fetch("https://api.forgium.dev/v1/prompt-integrations/pi_pub_x7y8z9w0/execute", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ input: { business_context: "..." }, turnstile_token: token, }), }); const body = await resp.json(); ``` ### 3. Handle response **Success:** ```json { "data": { "strengths": ["Item 1", "Item 2", ...], "weaknesses": ["Item 1", "Item 2", ...], "opportunities": ["Item 1", "Item 2", ...], "threats": ["Item 1", "Item 2", ...] } } ``` **Error:** ```json { "error": { "code": "VALIDATION_ERROR", "message": "Input does not match schema" } } ``` ### 4. Error codes | Code | Status | Meaning | |---|---|---| | `ORIGIN_DENIED` | 403 | Origin does not match manifest | | `TURNSTILE_INVALID` | 403 | Token missing, expired, or action mismatch | | `INTEGRATION_DISABLED` | 404 | Operator disabled the integration | | `RATE_LIMITED` | 429 | Per-IP burst limit (3/min) | | `DAILY_QUOTA_EXCEEDED` | 429 | Daily limit (24 successful/day) | | `REQUEST_TOO_LARGE` | 413 | Body exceeds limit | | `VALIDATION_ERROR` | 422 | Input does not match `input_schema` | | `MODEL_OUTPUT_INVALID` | 502 | Model returned invalid JSON | | `INFERENCE_UNAVAILABLE` | 503 | Workers AI unavailable | ### 5. Reset Turnstile after every attempt Tokens are single-use. Reset the widget after every submission, including failures, before allowing another attempt. ```js turnstile.reset(widgetId); ``` --- ## Security rules for the frontend 1. **No secrets in client code.** The bundle contains only public values. Never include the prompt, output schema, API key, or Turnstile secret. 2. **No innerHTML with backend data.** Render `data` fields as text, not HTML. 3. **Validate input client-side.** Enforce `minLength` / `maxLength` from the schema before sending. 4. **Respect Retry-After.** For 429 responses, disable submission until the retry window passes. --- ## Full flow ``` Browser Forgium agent-api │ │ │ POST /execute │ │ { input, turnstile_token } │ │ Origin: https://tu-empresa.com │ │ ─────────────────────────────────────▶ │ │ │ │ │ 1. Resolve integration │ │ 2. Check Origin (exact) │ │ 3. Rate limit (3/min per IP) │ │ 4. Validate input schema │ │ 5. Turnstile siteverify │ │ 6. Reserve daily quota │ │ 7. Workers AI (JSON mode) │ │ 8. Validate output schema │ │ │ { "data": { ... } } │ │ ◀───────────────────────────────────── │ ``` --- END docs/integration/prompt-integrations-example-foda.md --- --- BEGIN docs/integration/prompt-integration-agent-prompt.md --- # Prompts for AI agents Two prompts for integrating a form with Forgium Prompt Integrations. --- ## Prompt 1 — Setup The client pastes this into Claude Code alongside their existing form. Claude analyzes the form and returns an input schema and a suggested prompt. ``` Analizá este formulario y devolveme: 1. Un JSON Schema con los campos que envía el form (input_schema). 2. Un prompt que procese esos datos y devuelva una respuesta útil. Formato del input_schema: - type: "object" - additionalProperties: false - required: [campos obligatorios] - properties: cada campo con type, minLength/maxLength si aplica Formato del prompt: - Instrucciones claras para procesar los datos del form - Que devuelva JSON estructurado - Que no invente información que no esté en el input ``` ### What the client gets back ```json { "input_schema": { "type": "object", "additionalProperties": false, "required": ["nombre", "email", "consulta"], "properties": { "nombre": { "type": "string", "maxLength": 200 }, "email": { "type": "string", "maxLength": 300 }, "consulta": { "type": "string", "minLength": 10, "maxLength": 5000 } } }, "suggested_prompt": "Sos un asistente de atención al cliente. Analizá la consulta del usuario y devolvé una respuesta clara y accionable en JSON con: respuesta, categoria, urgencia." } ``` The client sends this to the Forgium operator. --- ## Prompt 2 — Integration The operator creates the integration via Admin API using the client's input_schema and prompt. Forgium returns a bundle. The operator pastes this into Claude Code alongside the client's form, replacing the placeholders with the bundle values. ``` Conectá este formulario con Forgium Prompt Integrations. Endpoint: Turnstile sitekey: Turnstile action: El endpoint espera un POST con JSON: { "input": { }, "turnstile_token": "" } Lee https://docs.forgium.dev/docs/integration/prompt-integrations para el contrato completo. ``` --- ## Full flow ``` Cliente Operador Forgium │ │ │ │ 1. Pega Prompt 1 + su form │ │ │ en Claude Code │ │ │ → obtiene input_schema │ │ │ + prompt sugerido │ │ │ │ │ │ 2. Te pasa schema + prompt │ │ │ ─────────────────────────────▶ │ │ │ │ │ │ │ 3. Creás integración │ │ │ (curl + admin key) │ │ │ ───────────────────────────▶ │ │ │ │ │ │ 4. Recibís bundle │ │ │ ◀─────────────────────────── │ │ │ │ │ 5. Le pasás Prompt 2 │ │ │ + bundle │ │ │ ◀───────────────────────────── │ │ │ │ │ │ 6. Pega Prompt 2 + bundle │ │ │ en Claude Code │ │ │ → integra el form │ │ │ │ │ │ 7. Deploya y funciona │ │ │ Form → Forgium → JSON │ │ ``` --- END docs/integration/prompt-integration-agent-prompt.md ---