Skip to main content

Agent SOAT Tools and Preset Parameters

This tutorial shows how to give an agent access to platform documents using soat tools — and how to use preset parameters to lock a tool to a specific document ID so the model never has to guess it.

You will:

  1. Log in as admin.
  2. Create a project and an Ollama AI provider.
  3. Create two documents: a public note and a private note.
  4. Create a user alice with a policy that restricts her to the public document path.
  5. Create three soat tools:
    • docs_list-documents — lists documents in the project.
    • docs_get-document — reads any document by ID (model supplies the ID).
    • docs_update-document — updates the public document (ID is preset; model never sees it).
  6. Create an agent that uses these tools and attach it to alice's project.
  7. Run a generation as alice and observe the agent updating the correct document without being told its ID.
  8. Verify that alice cannot read or update the private document (permissions enforcement).

By the end you will understand:

  • How to wire agent-side document tooling.
  • How preset_parameters eliminates the probabilistic risk of the model choosing the wrong ID.
  • How IAM policies are enforced even when an agent calls platform APIs on behalf of a user.

Prerequisites

  • SOAT running locally with Ollama. Follow the Quick Start guide.
  • An Ollama instance accessible at http://ollama:11434 with model qwen2.5:0.5b pulled (ollama pull qwen2.5:0.5b).
  • CLI, SDK, or curl available. The server is at http://localhost:5047.
export SOAT_BASE_URL=http://localhost:5047

Step 1 — Log in as admin

Admin is the built-in superuser role. It bypasses policy evaluation entirely. See IAM — Authentication for details on JWT tokens and the admin role.

soat login-user --username admin --password Admin1234!
soat configure # paste the token when prompted

Step 2 — Create a project

Every resource in SOAT lives inside a project. Create one to hold the agent, documents, and tools.

PROJECT_ID=$(soat create-project --name "Notes Project" | jq -r '.id')
echo "Project: $PROJECT_ID"

Step 3 — Create an Ollama AI provider

Set up a local AI provider backed by Ollama. This tutorial uses a local Ollama provider so it can run without external credentials. To connect xAI, OpenAI, Anthropic, or Amazon Bedrock instead, see Connect Third-Party LLMs.

PROVIDER_ID=$(soat create-ai-provider \
--project-id "$PROJECT_ID" \
--name "Ollama" \
--provider "ollama" \
--default-model "qwen2.5:0.5b" | jq -r '.id')
echo "Provider: $PROVIDER_ID"

Step 4 — Create documents

Create two documents: a public note the agent will update, and a private note it must not touch.

PUBLIC_DOC_ID=$(soat create-document \
--project-id "$PROJECT_ID" \
--title "Public Note" \
--content "Initial public content." \
--path "/notes/public/note.txt" | jq -r '.id')
echo "Public doc: $PUBLIC_DOC_ID"

PRIVATE_DOC_ID=$(soat create-document \
--project-id "$PROJECT_ID" \
--title "Private Note" \
--content "Confidential information." \
--path "/notes/private/note.txt" | jq -r '.id')
echo "Private doc: $PRIVATE_DOC_ID"

Step 5 — Create user alice with a restricted policy

Alice is allowed to run agent generations and access documents under /notes/public/*. She cannot read or modify documents at other paths. See Users, Policies, and IAM — SRNs for the full access-control model.

ALICE_ID=$(soat create-user --username alice-agent-soat-tools --password Alice1234! | jq -r '.id')
echo "Alice: $ALICE_ID"

POLICY_ID=$(soat create-policy \
--name "alice-agent-soat-tools-notes-policy" \
--document '{
"statement": [
{
"effect": "Allow",
"action": ["agents:CreateAgentGeneration"]
},
{
"effect": "Allow",
"action": ["documents:ListDocuments"]
},
{
"effect": "Allow",
"action": ["documents:GetDocument", "documents:UpdateDocument"],
"resource": ["soat:'"$PROJECT_ID"':document:/notes/public/*"]
}
]
}' | jq -r '.id')

soat attach-user-policies \
--user-id "$ALICE_ID" \
--policy-ids '["'"$POLICY_ID"'"]'

Step 6 — Create soat tools

Create three agent tools. Notice the third tool — docs-write — has preset_parameters containing the public document's ID. The key uses camelCase (documentId) because soat tool schemas use camelCase property names internally. The model will never see the documentId field; it will be injected automatically at call time.

# Tool 1 — list documents
LIST_TOOL_ID=$(soat create-agent-tool \
--project-id "$PROJECT_ID" \
--name "docs" \
--type soat \
--actions '["list-documents"]' | jq -r '.id')

# Tool 2 — read any document (model supplies document_id)
READ_TOOL_ID=$(soat create-agent-tool \
--project-id "$PROJECT_ID" \
--name "docs" \
--type soat \
--actions '["get-document"]' | jq -r '.id')

# Tool 3 — update the public document (document_id is preset)
WRITE_TOOL_ID=$(soat create-agent-tool \
--project-id "$PROJECT_ID" \
--name "docs" \
--type soat \
--actions '["update-document"]' \
--preset-parameters '{"documentId": "'"$PUBLIC_DOC_ID"'"}' | jq -r '.id')

echo "List: $LIST_TOOL_ID"
echo "Read: $READ_TOOL_ID"
echo "Write: $WRITE_TOOL_ID"

The three tool names the model will see at runtime are:

Tool nameActiondocument_id visible to model?
docs_list-documentslist documentsN/A
docs_get-documentread a documentyes — model supplies it
docs_update-documentupdate a documentno — injected from preset_parameters

Step 7 — Create the agent

Create the agent and attach all three tools. The agent's instructions guide the model to use its tools when answering requests.

AGENT_ID=$(soat create-agent \
--project-id "$PROJECT_ID" \
--ai-provider-id "$PROVIDER_ID" \
--name "Notes Agent" \
--instructions "You are a note-taking assistant. Use your tools to list, read, and update documents." \
--tool-ids "[\"$LIST_TOOL_ID\", \"$READ_TOOL_ID\", \"$WRITE_TOOL_ID\"]" | jq -r '.id')
echo "Agent: $AGENT_ID"

Step 8 — Log in as alice and run a generation

Alice asks the agent to update the public note via a session. The agent will call docs_update-document without knowing the document ID — the server injects it from preset_parameters.

# Log in as alice
ALICE_TOKEN=$(soat login-user --username alice-agent-soat-tools --password Alice1234! | jq -r '.token')

# Run the generation
RESULT=$(curl -s -X POST "$SOAT_BASE_URL/api/v1/agents/$AGENT_ID/generate" \
-H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "Please update the public note with the content: Updated by the agent."
}
]
}')

echo "$RESULT" | jq '.'

Step 9 — Verify the update and permissions

Confirm the agent updated the public document and was blocked from accessing the private one. This demonstrates how IAM policies enforce path-based access at runtime.

Confirm the public document was updated

soat get-document --document-id "$PUBLIC_DOC_ID" | jq '.content'
# Expected: "Updated by the agent."

Confirm alice cannot read the private document

curl -s "$SOAT_BASE_URL/api/v1/documents/$PRIVATE_DOC_ID" \
-H "Authorization: Bearer $ALICE_TOKEN" | jq '.'
# Expected: 403 Forbidden

The private document is inaccessible. If you asked the agent to update the private note, it would receive a 403 when trying to call docs_get-document with the private document's ID, and would report back that it is not permitted.


What happened

  1. Tool creation with preset_parameters: When you created docs-write, you stored { "documentId": "<public doc id>" } alongside the tool. The server stripped documentId from the schema before registering the tool with the model (preset keys must use the camelCase form of the parameter name).

  2. Model's view: The model saw docs_update-document accepting only content, title, path, metadata, and tags — no documentId in sight. This eliminates the risk of the model supplying a wrong or hallucinated ID.

  3. Execution: When the model called docs_update-document, the server merged the preset document_id back in before dispatching the PATCH /api/v1/documents/{document_id} request.

  4. Permission enforcement: The request ran under alice's JWT. The platform's document permission check verified that alice's policy allows documents:UpdateDocument for the path /notes/public/note.txt. The private document path falls outside /notes/public/*, so any attempt there returns 403.


Next steps

  • Add more actions to the tools (e.g., search-documents) for richer agent workflows.
  • Use step rules to force the agent to call a specific tool first.
  • Explore boundary policies to limit which actions agents can use at the agent level, independent of caller IAM policies.
  • Read the agents module reference for the full list of soat actions and configuration options.