Skip to main content
Model-agnostic skills keep a token-cheap stub (a name and a one-line description) in every request and load the full body only when a request matches. The bodies live in an object store you connect once. Any S3-compatible store works: AWS S3 in the cloud, or the minio container that ships with the stack for local use The store credentials are entered a single time by an admin on the Skills page. The secret is encrypted before it touches the database and is never returned by any read; the gateway is the only component that decrypts it, and it does so server-side to fetch a skill body. End users never see or enter a key, and a skill row itself carries only a content_url, never a credential
You configure the store once. Every s3:// skill reuses that one connection. Which users may see which skill is controlled separately by access groups, not by handing out keys

Option A: AWS S3

Use this for a real cloud bucket shared across environments The walkthrough below uses a bucket named forceai-skills in us-east-1 holding one object, pdf.md. Substitute your own names as you go
1

Create the bucket and upload a skill file

In the AWS console go to S3 -> Create bucket. Give it a name such as forceai-skills and pick a region such as us-east-1. Leave “Block all public access” on; the gateway reaches objects with credentials, never public URLs. Upload your skill body as an object, for example pdf.md, so its key is forceai-skills/pdf.md
2

Create a scoped IAM access key

This is the one value the ForceAI form cannot generate for you; it comes from AWS. In IAM -> Users -> Create user add a user such as forceai-skills-loader with no console access, then attach an inline policy scoped to just this bucket:
s3:GetObject is required so the gateway can load a body on demand. s3:PutObject is only needed if you will use the in-dashboard “Upload body” mode; drop it if you always upload objects yourself. Then under Security credentials -> Create access key copy the Access key ID (starts with AKIA) and the Secret access key. The secret is shown once, so copy it now
3

Open the store panel in ForceAI

Sign in to the dashboard at http://localhost:3000 (default admin / your LITELLM_MASTER_KEY). In the left navigation open Skills, then select the Model-agnostic skills tab. The store lives in the single row under the “Object store (S3-compatible)” heading
4

Fill the store row field by field

The row is left to right: a provider dropdown, then four text boxes, then the save button. Set each one:The region does not have a field in this row and defaults correctly for us-east-1. For any other region use the API form, which takes region explicitly
5

Save

Click Save store. The tag at the end of the row flips from not configured to configured, and field 5 changes to show “secret set (leave blank to keep)”. The secret is encrypted at rest and is never returned again; on a later edit you can leave field 5 blank to keep the stored one
For AWS S3 leave the Endpoint URL (field 3) blank. Pointing it at a minio-style URL is the most common misconfiguration and makes every load fail

Option B: local minio

The stack ships a minio container on port 9000 with its console on 9001, so you can prove the whole flow with no cloud account
1

Create the bucket in minio

Open the minio console at http://localhost:9001 and sign in with the dev credentials forceai / forceai-minio-dev (or your MINIO_ROOT_USER / MINIO_ROOT_PASSWORD overrides). Go to Buckets -> Create Bucket and add skills
2

Pick credentials

Reuse the root credentials above, or create a dedicated pair under Access Keys -> Create for least privilege
3

Enter the connection in ForceAI

On the Skills page Model-agnostic Skills tab set:Click Save
The endpoint must be http://minio:9000, using the container name minio, not localhost. The gateway resolves it on the internal docker network, where localhost would point at the gateway itself

Add a skill

With the store showing configured, click + Add skill on the same panel. A dialog opens with a mode toggle at the top and four fields below
1

Pick the source mode

Leave the toggle on Reference URL to point at an object that already exists in the bucket (this is the common case). Switch to Upload body only when you want to paste the SKILL.md text and have ForceAI write it to the bucket for you; that mode needs the s3:PutObject permission from step 2 above
2

Fill the fields

For the forceai-skills/pdf.md object from the walkthrough:
3

Save

Click OK. The skill appears in the table below with its Enabled switch already on. Leave it enabled
Enter the content URL as s3://forceai-skills/pdf.md, not as https://forceai-skills.s3.us-east-1.amazonaws.com/pdf.md. The s3:// form keeps the bucket private and lets the gateway authenticate with your stored key. The https:// form only works if you make the object publicly readable, which defeats the point of the access key

Verify a skill loads

Enable skills on a request and confirm the body is injected for that turn:
Open the request in Logs, expand the Raw view, and look at spend_logs_metadata.forceai_skills. It records available, loaded, loaded_names, and tokens_loaded, so you can confirm the matching skill was fetched from the store and injected while the non-matching skills were never loaded

Configure the store by API

The dashboard form is a thin wrapper over one admin-only endpoint on the control plane. The secret is write-only: it is accepted here and never returned by the matching GET
For minio, set "provider": "minio" and add "endpoint_url": "http://minio:9000". On an update you may omit secret_access_key to keep the stored one
GET /forceai/skills/store returns the provider, bucket, endpoint, region, and a secret_set boolean only. It never returns the secret itself