ReferenceAgents

Dens — a machine your agent keeps

A den is a persistent Linux machine per thread. It sleeps when idle and costs storage only; it wakes when you, a schedule, or an event need it, with files, services and context intact.

Updated 9/16/2026

A den is a real machine your agent keeps between turns — a Linux VM with a persistent disk. Install dependencies, run the app, leave files around: they are there next time. When nobody needs it, it sleeps and you pay for the disk only; the first prompt, portal visit or schedule wakes it (about a minute).

Starting one

On an agent, choose New thread in den. Pick a size (d1.small by default) and, when the agent has a GitHub connection with a repository bound, the repository is checked out at /den/repo. The thread's Den tab shows the status chip, the settings, the attached threads and the ledger of everything that happened on the machine.

Everything that survives sleep lives under /den:

/den/home           the agent's home
/den/repo           the checkout, when a repository is bound
/den/.connect0/     den.yaml state, logs (services, setup, commands)

What the agent can do

Five tools are on the belt whenever the thread has a den:

ToolWhat it does
den_execRun a shell command (bash -lc) under /den. Output is capped at 64 KB; a truncated result names the full log to read.
den_readRead a text file under /den, optionally a byte range.
den_writeWrite a text file under /den; parent folders are created.
den_id_tokenMint a fifteen-minute identity token for an audience (below).
den_scheduleWake the den on a cron or at an instant and start a run with a prompt (below).

Paths never leave /den. Every call is a row in the den's ledger.

.connect0/den.yaml

Setup is data, committed with the repository:

version: 1
size: d1.small           # d1.small | d1.medium | d1.large | d1.xlarge
setup:                   # runs once after clone, again when this file changes
  - pnpm install
services:
  web:
    cmd: pnpm dev
    port: 3000           # or omit → $PORT is assigned
    portal: true         # reachable through a portal (below)
secrets:                 # by reference — the value never touches the disk
  DATABASE_URL: connection:9c1e…   # a connection id from your Connections page
env:
  NODE_ENV: development
idle_minutes: 15

Services are supervised: they start on every wake with $PORT set, are restarted when they exit, and are left alone after five exits in ten minutes until the next wake. Their logs are under /den/.connect0/logs/services/.

Portals

A service declared with portal: true is reachable at https://<den>-<port>.den.connect0.ai. Anyone on the thread opens it from the Den tab; the service sees who is looking:

X-Connect0-Authenticated: member=yes; participant=driver; public=no
X-Connect0-Account-Id: …
X-Connect0-User-Id: …          (members only)
X-Connect0-User-Email: …       (members only)

Drivers can Share publicly… for one hour, three hours, a day or a week. A public link works without signing in, carries no identity (member=no; participant=none; public=yes), and can be revoked — it stops within a minute. A portal visit while the den sleeps shows Waking your den… and continues when it is up.

Terminal and files

The Den tab has a shared terminal for drivers — one tmux session on the machine, so two people driving the thread see the same screen — and a file browser: viewers read, drivers edit. Browsing files does not wake a sleeping den; the Wake button does.

Workload identity

Instead of copying a cloud credential onto the machine, let the machine prove who it is. den_id_token (or denctl id-token --audience … inside the den) mints a fifteen-minute OIDC token:

iss  https://auth.connect0.ai/oidc
sub  den:<den id>
aud  <what you asked for>
connect0: { account_id, project_id?, agent_id, thread_id?, run_id?, den_id, user_id?, email? }
act:  { sub: user:<id> }        — when a person triggered the run

The issuer's discovery document is https://auth.connect0.ai/oidc/.well-known/openid-configuration; keys rotate and the JWKS carries the previous key for the overlap. Every mint is a row in the den's ledger. Three ways to trust it:

Google Cloud — workload identity federation

gcloud iam workload-identity-pools create connect0 --location=global
gcloud iam workload-identity-pools providers create-oidc dens \
  --location=global --workload-identity-pool=connect0 \
  --issuer-uri="https://auth.connect0.ai/oidc" \
  --attribute-mapping="google.subject=assertion.sub,attribute.account=assertion.connect0.account_id" \
  --attribute-condition="assertion.connect0.account_id == '<your account id>'"
gcloud iam service-accounts add-iam-policy-binding den-runner@<project>.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="principalSet://iam.googleapis.com/projects/<number>/locations/global/workloadIdentityPools/connect0/attribute.account/<your account id>"

Inside the den:

denctl id-token --audience "//iam.googleapis.com/projects/<number>/locations/global/workloadIdentityPools/connect0/providers/dens" > /tmp/den.jwt
gcloud iam workload-identity-pools create-cred-config \
  projects/<number>/locations/global/workloadIdentityPools/connect0/providers/dens \
  --service-account=den-runner@<project>.iam.gserviceaccount.com \
  --credential-source-file=/tmp/den.jwt --output-file=/tmp/gcp-creds.json
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp-creds.json

AWS — an IAM OIDC identity provider

Create an identity provider with the issuer https://auth.connect0.ai/oidc and the audience you will request (say sts.amazonaws.com), then a role whose trust policy matches the den:

{
  "Effect": "Allow",
  "Principal": { "Federated": "arn:aws:iam::<account>:oidc-provider/auth.connect0.ai/oidc" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": { "auth.connect0.ai/oidc:aud": "sts.amazonaws.com" },
    "StringLike":   { "auth.connect0.ai/oidc:sub": "den:*" }
  }
}
aws sts assume-role-with-web-identity --role-arn arn:aws:iam::<account>:role/den-runner \
  --role-session-name den --web-identity-token "$(denctl id-token --audience sts.amazonaws.com)"

HashiCorp Vault — JWT auth

vault auth enable jwt
vault write auth/jwt/config oidc_discovery_url="https://auth.connect0.ai/oidc" bound_issuer="https://auth.connect0.ai/oidc"
vault write auth/jwt/role/den role_type=jwt user_claim=sub bound_audiences=vault \
  bound_claims='{"connect0.account_id":"<your account id>"}' token_policies=den
vault write auth/jwt/login role=den jwt="$(denctl id-token --audience vault)"

Secrets by reference

When a system cannot trust an issuer yet, name a connection in den.yaml: DATABASE_URL: connection:<id>. At every wake the value is resolved from your account's connection (a database URL, an API key, a GitHub installation token) and placed in the environment of the services and of every command the agent runs. It is never written under /den — which is why it must not be a file: the file browser shows everything. Each refresh is a ledger row naming the variables, never the values.

Waking on a schedule

A den can wake itself. The agent sets a rule with den_schedule — a five-field cron in UTC (no more often than every fifteen minutes) or a one-off instant, plus the prompt the run should start with:

den_schedule({ cron: "0 * * * *", prompt: "Triage new errors; open a thread per new issue" })

Drivers add, disable, enable and remove rules from the Den tab's Schedule section — the agent's included. When a rule fires, a run starts on its thread, the den wakes, and the ledger says so. At most 24 firings a day per den and ten enabled rules; a rule that would exceed the account's awake-minutes cap is skipped and logged, never run to fail.

Sleeping, waking, cost

A den sleeps after idle_minutes with no run, no command, no portal request and no terminal. Keep awake holds it up for up to eight hours at a time. Awake time is billed by the minute at the size's rate; a sleeping den is billed for its disk only. Per account there are caps on how many dens exist and how many are awake at once; the Den tab says when one is reached.

Destroy deletes the machine and its disk; the ledger stays.

What this does not do

  • A den is one machine; there is no autoscaling and no multi-machine den.
  • A sleeping den still costs its disk; it is not free, it is storage-only.
  • Waking takes about a minute; a portal or a prompt that arrives while it sleeps waits for that.
  • Public portal links carry no identity and expire; they cannot be made permanent.
  • An identity token lives fifteen minutes and cannot be refreshed; ask for a new one.
  • A wake rule fires within a minute of its instant, at most 24 times a day, and never more often than every fifteen minutes.
  • Secrets by reference are injected as process environment only; they are never written to the disk.

Ask Zero

Ask a question about connect0 and get an answer grounded in the docs, with links to the sources. Signed in? Zero answers with your account in mind.