> ## Documentation Index
> Fetch the complete documentation index at: https://gateway.forceaisecurity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Knowledge Catalog (OKF)

> Publish domain knowledge as OKF bundles and have the gateway inject the concepts that match a turn into the model call, in real time, on an opted-in request.

The Knowledge Catalog stores domain knowledge as [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) (OKF) bundles and grounds a model call on them at request time. A bundle is a set of markdown files with YAML frontmatter; on an opted-in request the gateway picks the concepts that match the user's turn and injects them as one system message before the provider is called, so GPT, Claude, and Gemini all answer from your knowledge without the client resending it. Grounding is opt-in per request through a single metadata field, so a call is only ever touched when you ask for it

<Info>
  OKF is markdown plus YAML frontmatter under an Apache-2.0 spec, so a bundle you author here is portable to any other OKF tool. ForceAI adds the catalog, the governed store, and the injection hook that makes an agent actually use it
</Info>

## How it fits together

Import and grounding run on two planes. The catalog API runs on the control plane, the grounding hook runs on the data plane, and both read and write one shared context store. That is why a bundle you import through the dashboard is available to every model call the gateway serves

```mermaid theme={null}
flowchart LR
  A["Agent / App / curl"]

  subgraph CP["Control plane"]
    B["OKF Catalog API<br/>import · concepts · graph · export"]
  end

  subgraph DP["Data plane (gateway)"]
    C["OKF grounding hook<br/>async_pre_call_hook"]
    D["LLM router"]
  end

  subgraph ST["Shared context store (S3 or filesystem)"]
    E[("Bundles as versioned<br/>tar.gz artifacts")]
  end

  A -- "POST /v1/chat/completions<br/>metadata.forceai_okf" --> C
  B -- "put_version" --> E
  C -- "load_bundle" --> E
  C --> D
  D -- "provider call" --> F["LLM provider"]
```

<Warning>
  The shared store is not optional. Import writes on the control plane and grounding reads on the data plane, so both must point at the same store or the gateway never sees your bundles and grounding silently does nothing (it fails open). In production set `FORCEAI_CONTEXT_BACKEND=s3` plus the bucket and endpoint on both the backend and the gateway
</Warning>

## The real-time flow

This is what happens on every grounded request, in the few milliseconds before the provider is called

```mermaid theme={null}
sequenceDiagram
  participant Ag as Agent / App
  participant GW as Gateway
  participant Hook as OKF grounding hook
  participant St as Context store
  participant M as LLM provider

  Ag->>GW: POST /v1/chat/completions<br/>metadata.forceai_okf = "sales_catalog"
  GW->>Hook: async_pre_call_hook(request)
  Hook->>St: load_bundle(bundle, version)
  St-->>Hook: OKF bundle (concepts)
  Hook->>Hook: drop concepts the caller may not see (access_groups)
  Hook->>Hook: score by tags + text, take top 3
  Hook->>Hook: inject one system message + stamp the spend log
  Hook-->>GW: request with knowledge prepended
  GW->>M: prompt (system knowledge + user turn)
  M-->>GW: grounded answer
  GW-->>Ag: response, audit in spend_logs_metadata.forceai_okf
```

<Steps>
  <Step title="The caller opts in">
    The request carries `metadata.forceai_okf`. Without it the hook returns immediately and the call is untouched
  </Step>

  <Step title="The hook loads the bundle">
    It reads the named bundle, and a pinned version if you gave one, from the shared context store
  </Step>

  <Step title="Access groups are applied">
    A concept whose frontmatter lists `access_groups` is dropped unless the caller's access groups intersect it, so a caller is only grounded on knowledge it is entitled to
  </Step>

  <Step title="The best concepts are selected">
    Every remaining concept is scored against the user's last message, tags counting double and prose once. The top three are kept; `index.md` and `log.md` are never injected
  </Step>

  <Step title="Knowledge is injected and audited">
    The selected concepts are prepended as one system message and a summary is written to `spend_logs_metadata.forceai_okf`, then the provider call proceeds with the knowledge in context
  </Step>
</Steps>

## Author a bundle

A bundle is a directory of `.md` files, one concept per file. A `type` in the frontmatter is required; everything else is optional but improves how well a concept is retrieved and governed. Relative markdown links between files become edges in the concept graph

<CodeGroup>
  ```markdown index.md theme={null}
  ---
  type: index
  title: Sales Catalog
  okf_version: "0.1"
  ---
  Root index of the sales knowledge bundle. See [Acme Widget](./acme_widget.md)
  and the [Refund Policy](./refund_policy.md).
  ```

  ```markdown acme_widget.md theme={null}
  ---
  type: product
  title: Acme Widget
  description: Flagship widget product
  tags: [widget, hardware, flagship]
  ---
  The Acme Widget is our flagship product. Pricing is governed by the
  [Pricing Policy](./pricing_policy.md).
  ```

  ```markdown refund_policy.md theme={null}
  ---
  type: policy
  title: Refund Policy
  description: Refund and return terms
  tags: [refund, returns, policy]
  access_groups: [finance, support]
  ---
  Refunds are accepted within 30 days of purchase. See the
  [Pricing Policy](./pricing_policy.md) for restocking fees.
  ```
</CodeGroup>

`tags` is the strongest lever on retrieval because it is weighted twice as heavily as prose, so tag a concept with the words a user would actually say. `access_groups` gates who can be grounded on the concept. `index.md` and `log.md` are reserved names and are never injected into a prompt

## Import a bundle

Open the dashboard **Knowledge Catalog** page (admin only), give the bundle an id, add your `.md` files, and click **Import**. Valid files become concepts; invalid files are reported, not silently dropped. Select the bundle to browse its concepts and the concept graph. The same thing over the API:

```bash theme={null}
curl -s -X POST http://localhost:3000/forceai/okf/bundles/sales_catalog/import \
  -H "Authorization: Bearer $FORCEAI_KEY" -H "Content-Type: application/json" \
  -d '{
    "files": {
      "index.md": "---\ntype: index\ntitle: Sales Catalog\nokf_version: \"0.1\"\n---\nRoot index.",
      "refund_policy.md": "---\ntype: policy\ntitle: Refund Policy\ntags: [refund, returns]\n---\nRefunds within 30 days."
    }
  }'
# -> {"bundle_id":"sales_catalog","version":"v1","imported":2,"skipped":[],"errors":[]}
```

You can also post a gzip tarball with `Content-Type: application/gzip`, which is exactly what `GET /forceai/okf/bundles/{id}/export` returns, so bundles round-trip cleanly. Each import is a new version, so a re-import never overwrites the old one

## Ground a request in real time

This is the part an agent or app does on every turn. Add `metadata.forceai_okf` to the chat completion and the gateway does the rest. The value is a bundle id, or an object that pins a version:

<CodeGroup>
  ```bash curl theme={null}
  curl -s http://localhost:4000/v1/chat/completions \
    -H "Authorization: Bearer $FORCEAI_KEY" -H "Content-Type: application/json" \
    -d '{
      "model": "claude-haiku-4-5",
      "metadata": {"forceai_okf": "sales_catalog"},
      "messages": [{"role": "user", "content": "What is the refund window for the Acme Widget?"}]
    }' | jq -r '.choices[0].message.content'
  # -> "Refunds are accepted within 30 days of purchase..."
  ```

  ```python OpenAI SDK theme={null}
  from openai import OpenAI

  client = OpenAI(base_url="http://localhost:4000/v1", api_key=FORCEAI_KEY)

  resp = client.chat.completions.create(
      model="claude-haiku-4-5",
      messages=[{"role": "user", "content": "What is the refund window for the Acme Widget?"}],
      extra_body={"metadata": {"forceai_okf": "sales_catalog"}},
  )
  print(resp.choices[0].message.content)
  ```

  ```json pin a version theme={null}
  {
    "model": "claude-haiku-4-5",
    "metadata": {"forceai_okf": {"bundle": "sales_catalog", "version": "v2"}},
    "messages": [{"role": "user", "content": "..."}]
  }
  ```
</CodeGroup>

Streaming works the same way; injection happens before the first token, so `"stream": true` needs no extra handling. Without `forceai_okf` in the request the model runs ungrounded, which is the point: you decide per call

## How an agent uses it

Grounding keys off the request body, so whatever builds the model call sets the metadata. For a direct API or SDK client, set `metadata.forceai_okf` as above; this is the supported path today for any OpenAI-compatible caller. Agent, A2A, and orchestrator turns all pass through the gateway, so grounding fires as soon as the metadata is present on the turn

<Note>
  Attaching a bundle to an agent so its runtime stamps `forceai_okf` on every turn automatically, with no caller involvement, is a planned per-agent setting rather than a current option. Until then, ground an agent by setting the metadata wherever its turns originate
</Note>

## Tune what gets selected

Selection is lexical and deterministic, so you shape it by shaping the concepts. If the right concept is not being pulled in, add the user's vocabulary to that concept's `tags`, since tags outweigh prose. At most three concepts are injected per turn, which keeps the added tokens small and predictable. Split a sprawling document into several focused concepts rather than one large file, so the matcher can pick the relevant piece

## See what a request used

Open the request in **Logs** and expand it to see which concepts were injected, or read it back from the spend log. The summary is stamped into `spend_logs_metadata.forceai_okf`:

```json theme={null}
{
  "bundle": "sales_catalog",
  "available": 3,
  "loaded": 3,
  "loaded_paths": ["acme_widget.md", "refund_policy.md", "pricing_policy.md"],
  "tokens_loaded": 77
}
```

`available` is how many concepts the caller was allowed to see, `loaded` is how many were injected, and `loaded_paths` names them exactly

## Scope and safety

A caller is only ever grounded on concepts it could see, since `access_groups` filtering runs before selection. The hook fails open, so a store hiccup leaves the request untouched rather than blocking it. Injected knowledge is system context the model reads, so treat a bundle as content any grounded caller may see and keep secrets out of it

<Warning>
  Grounding only happens on requests that opt in, so a request without `metadata.forceai_okf` is never modified. A concept with no `access_groups` is visible to every caller of the bundle; add `access_groups` to restrict it
</Warning>

## API reference

Catalog routes are admin-gated and served on the control plane; chat completions are served on the data plane. Behind the dashboard both are the same origin, proxied by nginx, which is why the examples above use `http://localhost:3000` for catalog calls and `http://localhost:4000` for chat

| Method | Path                                 | Purpose                                    |
| ------ | ------------------------------------ | ------------------------------------------ |
| `POST` | `/forceai/okf/bundles/{id}/import`   | Import a bundle (JSON `{files}` or tar.gz) |
| `GET`  | `/forceai/okf/bundles`               | List bundle ids                            |
| `GET`  | `/forceai/okf/bundles/{id}/versions` | List versions of a bundle                  |
| `GET`  | `/forceai/okf/bundles/{id}/concepts` | List concepts (type, title, tags)          |
| `GET`  | `/forceai/okf/bundles/{id}/graph`    | Concept graph (nodes, edges, unresolved)   |
| `GET`  | `/forceai/okf/bundles/{id}/export`   | Download the bundle as tar.gz              |
| `POST` | `/v1/chat/completions`               | Ground a call with `metadata.forceai_okf`  |
