Skip to content

FAQ

General

What is Tilde?

Tilde combines three layers behind a single product: versioned object storage, sandboxed compute, and a mediated network. Each is isolated and audited, so you can run autonomous code (agents, pipelines, untrusted scripts) against real data without giving up reversibility, containment, or visibility into what the workload actually did. Every commit, every command, and every outbound request is recorded against the same identity.

How does versioning work?

Every change in Tilde goes through a session — a transactional workspace where writes are staged in isolation and only become visible to others when the session commits. Sessions are atomic: either the whole batch lands as a single commit, or none of it does.

There are two ways to interact with a session:

  • Sandboxes (the common path) — Tilde mounts your repository at /sandbox inside an isolated container. Your code reads and writes files normally; on a clean exit, all changes commit atomically. On a non-zero exit or cancellation, the session rolls back.
  • Direct session API — for scripts that don't need a container, the SDK exposes repo.session() to stage put/delete/copy operations programmatically and commit them yourself.

Either way, each commit records who, when, and why. The timeline lets you browse the full history and compare any two commits.

Does importing data copy it?

Yes. When you import data from an external source (S3, GCS), Tilde copies each object into its own storage. After import, reads are served directly from Tilde — the source is not accessed at read time. Source metadata (connector, path, ETag) is recorded on each entry for provenance.

What happens if the source data changes after import?

Because objects are copied during import, changes to the source have no effect on data already imported into Tilde. Each import is a point-in-time snapshot. To pick up changes in the source, run a new import job.


Sandboxes

What's a sandbox?

A sandbox is an isolated container that runs your code with the repository mounted as a versioned filesystem at /sandbox (configurable). Reads and writes look like normal file operations to your code; behind the scenes they're staged in a session that commits atomically when the sandbox exits cleanly.

Can I run any container image?

Yes. Pass any Docker image reference in the image field (for example, python:3.12, ubuntu:22.04, or ghcr.io/my-org/my-image:latest). Tilde pulls the image when the sandbox starts. See Compute Isolation.

Where does my data appear inside the container?

Your repository is mounted at /sandbox by default. Set mountpoint to use a different path, and path_prefix to mount only objects under a specific prefix.

What happens to my writes if the sandbox fails?

Sessions are atomic. Exit code 0 commits everything. Any non-zero exit, a cancellation, or a timeout rolls the session back — nothing reaches the repository. There is no partial commit.

How do I see what a sandbox produced?

Three live streams per sandbox:

  • GET .../sandboxes/{id}/logs/stdout — merged stdout + stderr (the runner interleaves both into one stream)
  • GET .../sandboxes/{id}/logs/network — NDJSON stream of every outbound HTTP request, with the proxy decision and upstream response metadata
  • GET .../sandboxes/{id}/terminal — WebSocket terminal (only for sandboxes created with mode: "interactive")

The Python SDK exposes these as status.stdout(), status.network(), and the high-level repo.shell() context manager.

Can I run an interactive shell?

Yes. From the CLI: tilde shell my-team/my-data --image python:3.12. From the SDK: use repo.shell(...) as a context manager (commands run inside, changes commit on clean exit).


Triggers

What's a sandbox trigger?

A trigger is a saved sandbox configuration that fires automatically when a commit lands matching a path condition. Use them for event-driven pipelines: validate uploads under datasets/, regenerate summaries when documents change, retrain when a labelled set is updated.

What conditions can a trigger match?

Two condition types, OR-combined:

  • prefix — fires when any changed file starts with the given prefix (e.g. datasets/)
  • path_exact — fires when a specific file changes, optionally filtered by diff_type (added, removed, changed, or any)

A single trigger can list up to 10 conditions; the trigger fires if any one matches.

Can triggers fire infinitely?

No. A trigger can fire on any commit (including commits made by another trigger's sandbox), but the resulting sandbox's commit will not fire further triggers. The depth limit is hard-coded to prevent recursion loops.

Can a trigger run as an agent?

Yes. Set run_as: { "type": "agent", "id": "..." } on the trigger. The sandbox executes under that agent's identity, authorized by the agent's inline policy intersected with its creator's effective policy (and gets its agent secrets injected). This is the right pattern for "an agent that responds to specific file changes".


Network

Why does my sandbox need network policies?

When you let autonomous code run with access to real data, it can do anything a program can do — including making HTTP requests. Without network controls a sandbox could exfiltrate data, abuse environment credentials, reach cloud metadata endpoints, hit private services, or download untrusted code. Network policies stop all of that at the proxy boundary.

What's blocked by default?

Two layers:

  1. Baseline rules unconditionally block dangerous destinations (RFC1918 private ranges, link-local + cloud-metadata 169.254.0.0/16, loopback, carrier-grade NAT, IPv6 equivalents). These cannot be overridden by policy.
  2. Network policies are deny-by-default. Without an explicit allow rule, no host is reachable.

What's allowed by default?

Every organization gets a built-in DefaultNetworkAccess policy that allows common package managers (npm, PyPI, crates, RubyGems, Go module proxy, Yarn), OS package managers (Debian/Ubuntu apt, Alpine apk), AI APIs (OpenAI, Anthropic), and GitHub raw + objects. See Network Isolation for the full host list.

How do I allow my sandbox to reach a specific service?

Attach a custom network policy with HttpRequest(host:"...") rules. Modifiers: host, scheme, port, method, path (all support fnmatch patterns). Example: HttpRequest(host:"*.example.com", scheme:"https").

Where can I see what was blocked?

Stream the per-sandbox network log: GET .../sandboxes/{id}/logs/network. Each line is a JSON object with the method, host, path, proxy decision (allow/deny/baseline_deny), the matching policy name (if allowed), and the upstream status code. Denied requests are logged at WARN level so they're easy to find during policy tuning.



Agents

What are agents?

Agents are non-human identities scoped to an organization, designed for automated tools and AI assistants. Each agent gets its own API key (prefixed tak-) and appears as a distinct committer in the audit trail.

How does agent approval work?

When Require approval for agent commits is enabled on a repository:

  1. An agent creates a session and stages changes as usual
  2. When the agent commits, the response returns HTTP 202 Accepted instead of completing immediately
  3. A human reviewer inspects the staged changes in the Tilde web UI
  4. The reviewer approves (creating the commit) or rolls back (discarding changes)

This gives you a human-in-the-loop gate for any agent-driven data changes.

Can multiple agents work on the same repository?

Yes. Each agent works in its own session, so their changes are fully isolated until committed. If approval is enabled, each agent's commit is reviewed independently.

How do I use Tilde with an AI assistant?

Install the Python SDK (pip install tilde-sdk) and use the Agent Skill for Claude Code or the CLI to let AI assistants interact with your repositories -- running sandboxes, browsing objects, making changes, and committing, all with the same approval controls.


Authentication

Which authentication method should I use?

Use case Recommended method
Local CLI / interactive use tilde auth login (device flow)
Python SDK on a developer machine Picks up CLI credentials automatically
Scripts, cron jobs, CI/CD User API key (tuk-...) via TILDE_API_KEY
Service-to-service / automation Role API key (trk-...)
AI agents and assistants Agent API key (tak-...)

Does the Python SDK pick up tilde auth login credentials automatically?

Yes. After you run tilde auth login, the CLI saves credentials to ~/.tilde/config.yaml. The Python SDK's resolution order is: explicit parameter > TILDE_API_KEY env var > ~/.tilde/config.yaml. So on a developer machine, after authenticating once with the CLI, tilde.repository("my-team/my-data") works with no further configuration.

My API key stopped working

Check that the key hasn't been revoked. List your keys with GET /api/v1/auth/keys and verify the key's revoked_at field is null. If it has been revoked, create a new key in the Tilde web UI.

Should I use a user key, a role key, or an agent key?

  • User key (tuk-) — acts as you. Useful for CI jobs that need your full permissions.
  • Role key (trk-) — non-human identity scoped to one organization, no metadata. Right for CI/CD pipelines and service accounts that should appear as a distinct principal in audit logs.
  • Agent key (tak-) — non-human identity that carries metadata. Grants come only from the agent's inline policy, intersected with its creator's effective policy, and can include approval requirements (?Action()). Right for AI assistants and pipelines that should run with bounded, inspectable permissions.

Repositories & Objects

What's the maximum object size?

Individual objects can be up to 5 GB.

How do I undo a commit?

Use the revert endpoint. Reverting creates a new commit that applies the inverse of the target commit's changes — files that were added are removed, files that were removed are restored, and modified files return to their pre-commit state. The original commit is preserved in history.

import tilde

repo = tilde.repository("my-team/my-data")
for commit in repo.commits.list():
    revert = commit.revert(message="Undo accidental upload")
    print(f"Revert commit: {revert.id}")
    break

See the Python SDK or Using the API for more details.

How does pagination work?

All list endpoints use keyset pagination:

  1. Request with an optional amount parameter (default: 100, max: 1000)
  2. Check the pagination.has_more field in the response
  3. If true, use pagination.next_offset as the after parameter for the next request

Connectors & Import

Which sources can I import from?

Two connector types are supported today:

  • S3 — any S3-compatible object store (AWS S3, MinIO, RustFS, custom endpoints)
  • GCS — Google Cloud Storage, authenticated with a service-account JSON

You can also do cross-repository imports — copy objects from another Tilde repo you have read access to (no connector required).

Can I import from a private S3 bucket?

Yes. Provide your AWS credentials (access key ID and secret access key) in the connector configuration. For buckets that require a specific region, set the region and optionally endpoint fields.

Can I use a custom S3-compatible service?

Yes. Set the endpoint field in the S3 connector configuration to your service URL. Tilde will use path-style addressing automatically for custom endpoints.

Can I import from Google Cloud Storage?

Yes. Create a connector with type: "gcs" and source_uri: "gs://my-bucket/prefix/". Authenticate with a service-account JSON in config.credentials_json. The GCS connector supports the same import flow as S3 — files are copied into Tilde at import time.

What happens if I delete a connector?

Deleting a connector is a soft delete. Since imported objects were copied into Tilde's storage during import, they remain fully readable. The connector is only needed during import, not for subsequent reads.

Can I re-import after a source changes?

Yes. Run a new import job with the same source and destination paths. The new import will create a new commit with the updated data. The previous state is still accessible via the old commit in the timeline.

Can I import data from another Tilde repository?

Yes. Use a cross-repository import by specifying the source organization and repository instead of a connector. The import copies objects from the source repository into the destination, creating a new commit. You must have read access to the source repository.

import tilde

repo = tilde.repository("my-team/my-data")
job = repo.imports.create_from_repository(
    repo_path="other-team/source-data",
    destination_path="external/",
    commit_message="Import from source-data",
)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
  -X POST "https://tilde.run/api/v1/organizations/my-team/repositories/my-data/import" \
  -H "Content-Type: application/json" \
  -d '{
    "source_organization": "other-team",
    "source_repository": "source-data",
    "destination_path": "external/",
    "commit_message": "Import from source-data"
  }'

How do I know if an import is reproducible?

All imports are inherently reproducible since objects are copied into Tilde's storage at import time. The source_metadata on each entry records the source path, ETag, and (when use_versioning: true) version ID for provenance tracking.


RBAC & Permissions

I just created an organization but can't do anything

The organization creator is automatically assigned the Owner built-in policy, which grants full access. If you're still getting 403 Forbidden, check:

  1. You're authenticated as the user who created the organization
  2. The organization name in the URL matches exactly
  3. Wait a few seconds for authorization caches to refresh (TTL is 3 seconds)

How do I give someone read-only access?

Attach the built-in ReadAll policy to their user or group. It grants list and read access to repositories, objects, members, and groups (commit history included).

Which built-in policies ship out of the box?

Six built-in policies are seeded into every organization:

Policy What it grants
Owner Full access to every action. Auto-attached to the organization creator.
SuperUser Everything except member/group/policy management and repository creation/deletion.
ReadAll Read-only on repositories, objects, members, groups, and commit history.
AgentManager Create/manage agents you own, plus view/cancel sandboxes and triggers run by those agents.
SandboxManager Create/list any sandbox or trigger; view, cancel, and manage only your own.
DefaultNetworkAccess Default outbound network policy for sandboxes (npm, PyPI, apt, AI APIs, GitHub, …).

See the RBAC Reference for the exact policy text of each.

How long do permission changes take to apply?

Authorization results are cached for 3 seconds. After changing policies or group memberships, it may take up to 3 seconds for the changes to take effect.

Can I restrict what agents can do?

Agents default to zero permissions. Grants come only from the agent's inline policy, intersected with the creator's effective policy — so an agent can never do more than its creator, and does nothing at all unless an inline policy is set.

A typical agent inline policy includes the sandbox lifecycle actions (needed to run any sandbox), some reads, and scoped writes:

# Sandbox lifecycle -- required to run a sandbox as this agent
CreateSandbox()
CreateSession()
CommitSession()
RollbackSession()

# Reads
ListRepositories()
GetRepository()
ListObjects()
GetObject()

# Scoped writes
PutObject(path:"outputs/*")

Use the ? (require approval) prefix to taint sessions so a human must approve commits:

?PutObject(path:"outputs/*")
?DeleteObject(path:"outputs/*")

How do I let users run their own sandboxes without seeing each other's?

Attach the built-in SandboxManager policy. It allows CreateSandbox() / ListSandboxes() for everyone, but scopes GetSandbox / CancelSandbox (and the trigger equivalents) to created_by:$principal.id — so each user only sees and cancels what they created themselves.

Who can read sandbox logs?

Whoever has the GetSandbox action for that sandbox. A single GetSandbox permission gates all read-side endpoints — details, status, the merged stdout/stderr stream, the network log, and the interactive terminal. There is no separate action per stream. Use the created_by or agent_created_by modifiers if you need to scope by ownership.

What's the difference between allow and deny?

For users and roles, Tilde uses deny-overrides-allow semantics:

  • If any policy denies an action, it's denied (regardless of allow rules in other policies)
  • If at least one policy allows and none deny, it's allowed
  • If no policy addresses the action, it's denied (default deny)

Agents use a different rule: the inline policy and the creator's effective policy are intersected — both must allow, and a deny in either denies. See the RBAC reference for details.


Troubleshooting

"UNAUTHORIZED" on every request

  1. Check credentials: Verify your API key is correct and not revoked.
  2. Check auth header format: API keys use Authorization: Bearer YOUR_API_TOKEN (not Basic or other schemes).
  3. Re-authenticate the CLI: If you're using tilde auth login, run it again — your saved credentials may have expired.

"FORBIDDEN" on a sandbox or trigger I created

You probably have CreateSandbox / CreateSandboxTrigger but not the per-resource GetSandbox / GetSandboxTrigger (or CancelSandbox / UpdateSandboxTrigger). The built-in SandboxManager policy scopes those to created_by:$principal.id — make sure you're authenticated as the same principal that created the sandbox.

"NOT_FOUND" for a repository I know exists

  1. Check the organization: Repository URLs include the organization slug. Verify you're using the correct organization.
  2. Check permissions: You may not have GetRepository permission. Ask an org owner to check your policies.
  3. Check soft deletes: The repository may have been deleted. Deleted repositories are not visible via the API.

My sandbox failed and nothing was committed

That's the intended behavior. Sessions are atomic — only a clean exit (status code 0) commits. Read the sandbox's error_message and exit_code fields, plus the merged logs/stdout stream, to figure out what went wrong. To debug interactively, re-run with tilde shell (or mode: "interactive").

My sandbox is hitting "DENY" on outbound requests

Stream the network log: GET .../sandboxes/{id}/logs/network. Each denied request includes the host, the proxy decision, and the reason (baseline_deny for unconditionally-blocked ranges, otherwise no policy matched). To allow a host, attach a network policy with HttpRequest(host:"..."). Note that baseline rules (RFC1918, cloud metadata, loopback) cannot be overridden.

My agent's commit returned 202 instead of committing

The repository has Require approval for agent commits enabled. The 202 response includes a web_url pointing to the approval page; a human with ApproveSessionChanges must visit it and approve or roll back the session. The Python SDK's commit() blocks on this by default; pass block_for_approval=False to return immediately.

Import job stuck in "running"

If an import job remains in "running" status for an unusually long time, it may have encountered a transient issue. Contact support with the job ID so the team can investigate.

Agent commit stuck in "pending approval"

If an agent commit is waiting for approval, a human reviewer needs to approve or roll back the changes in the Tilde web UI. Check with your team to ensure someone has the ApproveSessionChanges permission for the repository.

I need help with something not listed here

Contact the Tilde support team. When reporting an issue, include the request_id from any error response — it helps the team diagnose problems quickly.