Skip to main content
A Sandcastle is a TextQL-managed, per-session Python/bash sandbox. It comes with a working filesystem, a stateful Python kernel, governed outbound network, your org’s connectors, and — when context is enabled — a permissioned, RBAC-pruned mount of your org’s Context Library (our ontology / persisted-memory layer) at /sandbox/files/library. This guide shows how to wire your own agent loop into a Sandcastle. There are two ways to do it — pick the one that fits your architecture. The Sandcastle is the same either way; what changes is where the loop runs.
The Sandcastle is the execution environment — your agent never has to touch our infra directly. You either run your loop inside it and let the agent use the filesystem and tools natively, or run your loop in your own infra and execute commands inside the Sandcastle remotely over the Sandcastle API.

Prerequisites

WhatWhere
TextQL API keySettings → Developers → API Keys. Scopes the Sandcastle to your org and member, and gates the Context Library mount by your OWNERS permissions.
Model API keyWhichever LLM provider drives your loop (e.g. Anthropic). For the agent-inside mode this must be reachable through the Sandcastle’s egress proxy — see Outbound network.
Base URLhttps://app.textql.com (or your dedicated/self-hosted host).
All Sandcastle endpoints live under /v2/sandcastles and authenticate with Authorization: Bearer <TEXTQL_API_KEY>.

Choose your topology

Agent loop outside

Your loop runs in your infra. You boot a Sandcastle and execute commands inside it remotely. Best when you already own the orchestration, want your loop next to your other services, or need to fan one loop across many Sandcastles. Fully supported today.

Agent loop inside

Your loop runs inside the Sandcastle. Your orchestrating code just boots the Sandcastle, pushes the agent, and starts it. The agent then reads/writes the filesystem and calls tools natively — no round-trip per step. Possible today via exec; not yet a packaged template.

Option A — Agent loop outside the Sandcastle

This is the “virtual bash tool” model: the loop lives in your orchestrating code and treats the Sandcastle as a remote execution + filesystem backend. Every tool call your model makes becomes an API call against the Sandcastle.
1

Start (or restart) a Sandcastle

curl -X POST https://app.textql.com/v2/sandcastles \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -H "Content-Type: application/json" -d '{}'
# → { "sandbox_id": "org123-<uuid>", "created_at": "..." }
Pass back a sandbox_id to resume a specific Sandcastle; omit it for a fresh one. The Sandcastle is keyed to your member, and its /sandbox/files/library mount is pruned to what your OWNERS permissions allow.
2

Wire each tool the model can call to an endpoint

Model toolSandcastle endpointNotes
bash(cmd)POST /v2/sandcastles/:id/execFresh process per call (no shared shell state). kind: "bash" or "python".
python(code)POST /v2/sandcastles/:id/executeStateful kernel — variables and dataframes persist across calls.
read_file / write_file / lsGET/POST/DELETE /v2/sandcastles/:id/files…Upload, download, list, delete files in the working dir.
query_connectorPOST /v2/sandcastles/:id/queryRuns SQL/TQL against an org connector and lands a dataframe in the kernel.
3

Run your loop

# your infra
sandbox_id = start_sandcastle()
messages = [{"role": "user", "content": task}]

while True:
    resp = model.create(messages=messages, tools=TOOLS)
    if resp.stop_reason != "tool_use":
        break
    for call in resp.tool_calls:
        if call.name == "bash":
            out = http.post(f"/v2/sandcastles/{sandbox_id}/exec",
                            json={"command": call.input["cmd"]})
        elif call.name == "python":
            out = http.post(f"/v2/sandcastles/{sandbox_id}/execute",
                            json={"code": call.input["code"]})
        messages.append(tool_result(call.id, out))
4

Read the Context Library mount

The library is mounted read-only-ish at /sandbox/files/library. Your loop can cat/grep it like any directory:
curl -X POST https://app.textql.com/v2/sandcastles/$ID/exec \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -d '{"command": "grep -ri \"revenue definition\" /sandbox/files/library"}'
Use execute (stateful kernel) for an iterative data-analysis loop where the model builds up dataframes across turns, and exec (one-shot) for filesystem/shell steps that don’t need shared state.

Option B — Agent loop inside the Sandcastle

Here the loop runs in the Sandcastle. Your orchestrating code’s only job is to boot the Sandcastle, push your agent code, and start it. The agent then uses the local filesystem and /sandbox/files/library mount directly — no per-step round-trip — and makes model/tool calls out through the Sandcastle’s egress proxy.
1

Start a Sandcastle and push your agent

# 1. start
ID=$(curl -s -X POST https://app.textql.com/v2/sandcastles \
  -H "Authorization: Bearer $TEXTQL_API_KEY" -d '{}' | jq -r .sandbox_id)

# 2. push your agent code
curl -X POST https://app.textql.com/v2/sandcastles/$ID/files \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -F "file=@agent.py"
2

Run the loop inside

curl -X POST https://app.textql.com/v2/sandcastles/$ID/exec \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -d '{"kind":"bash",
       "command":"python3 /sandbox/files/agent.py",
       "env":{"ANTHROPIC_API_KEY":"'"$ANTHROPIC_API_KEY"'"}}'
Inside agent.py, your tools are just local calls — open(), subprocess, glob, grep over /sandbox/files/library. The agent never leaves the box for a tool step; it only leaves the box to call the model.

Outbound network

Sandcastle egress flows through a forward proxy that records every outbound call (host, status, bytes, duration) to an egress ledger and enforces an allowlist — you can see this in the Network tab of any Sandcastle. For the agent-inside topology this matters:
  • The model endpoint your loop calls (e.g. api.anthropic.com) must be permitted by the egress policy, or the call shows up as denied in the ledger.
  • In-cluster TextQL services are blocked from the Sandcastle except the sandbox proxy/query ports, so the agent reaches connectors and the Context Library through the provided mounts and the /query endpoint, not by calling internal services directly.
The agent-inside topology is achievable today through exec, but it is not yet a packaged template (no first-class “agent image”, no managed secret injection beyond per-call env). If you want fully managed in-sandbox agents, prefer Option A for now and talk to us about the roadmap.

Persisting changes back to the Library (writeback)

Both topologies can read /sandbox/files/library, and both can write back to it. Edits an agent makes to that mount are diffed against a baseline snapshot (/sandbox/files/.internal/library.snapshot) and surfaced as a patch for review/approval before they land in canonical git — the same pipeline whether the edit comes from the in-product chat tool or the Sandcastle API.
1

Stage edits in the mount

curl -X POST https://app.textql.com/v2/sandcastles/$ID/exec \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -d '{"command":"echo \"## Revenue\nNet sales recognition policy...\" > /sandbox/files/library/metrics/revenue.md"}'
2

(Optional) preview what would be written back

curl https://app.textql.com/v2/sandcastles/$ID/library/diff \
  -H "Authorization: Bearer $TEXTQL_API_KEY"
# → { "has_changes": true, "diffs": [...], "raw_diff": "..." }
3

Author a patch

curl -X POST https://app.textql.com/v2/sandcastles/$ID/library/patches \
  -H "Authorization: Bearer $TEXTQL_API_KEY" \
  -d '{"title":"Add Q3 revenue definition","description":"Documents net-sales recognition under metrics/."}'
# → { "patch_number": 42, "status": "open", "has_conflicts": false, ... }
The patch is submitted for admin review (open), unless an auto-approve rule matches (approved, already live). See Create Library Patch.
Writeback is an explicit, reviewed action — not a background auto-sync. Permissions are enforced twice (the mount is pruned to your OWNERS access, and every changed path is re-validated at merge), so a patch can never widen access. If the library has drifted you’ll get has_conflicts: true; resolve the .rej markers and re-submit with the same patch_number.

How this maps to the Library backend

When context is enabled, /sandbox/files/library is materialized per session from your org’s canonical, git-versioned Context Library:
  • On FSx ONTAP deployments, the worker mounts a writable FlexClone of the latest approved snapshot, pruned in-place to your OWNERS permissions — copy-on-write, so startup is fast and isolated per session.
  • On the default filestore backend, permitted contents are copied into the session tree.
Either way the mount is RBAC-scoped at materialization time and .git / OWNERS metadata is stripped or locked, so a Sandcastle only ever sees the slice of org memory the caller is allowed to see.