Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions managed-agents/self-hosted-sandboxes/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,31 @@ design. This file is the runbook.
1. **Invoke `/claude-api` first** for the Managed Agents reference (agents,
environments, sessions, memory stores). Don't guess field names.
2. **Check the host**: `docker version` works for this user, `ant --version`
is 1.23 or later, `jq` is on PATH, and `ant auth status` shows a login
(or the user puts `ANTHROPIC_API_KEY` in `.env`). Nothing else: the
Python SDK only runs inside the `docker-memory/` image.
3. **`./agents/setup.sh`** creates the resources from `agents/*/*.yaml` and
appends their IDs to `.env`. Re-running updates in place.
is 1.30 or later (the first release with `ant apply`), `jq` is on PATH,
and `ant auth status` shows a login, or the user exports
`ANTHROPIC_API_KEY` (the CLI does not read `.env` on its own). Nothing
else: the Python SDK only runs inside the `docker-memory/` image.
3. **`ant apply --yes .`** from the demo's directory creates the resources
from `agents/*.md`, `environments/*.yaml`, and `memory_stores/*.yaml`,
and records their IDs in `./claude-lock.json`. You have no terminal for
its confirmation prompt, hence `--yes`. Run `ant apply --dry-run .` first
if you want to show the user the plan. Re-running after a file edit
updates the same resources in place as a new agent version. If it says
`refusing to apply`, a resource was changed or archived in the Console:
show the user the reason it printed before reaching for `--force`.
4. **The one step you can't do for the user**: the environment key. They
mint it in the Console (Environments, the environment `setup.sh` just
created, Keys) and add `ANTHROPIC_ENVIRONMENT_KEY=...` to `.env` (the
example line is commented out). Ask them to paste it there. Never echo it
or log it.
mint it in the Console (Environments, the environment `ant apply` just
created, Keys). Its `env_...` ID is in the apply output and in
`claude-lock.json`. Then they `cp .env.example .env` and add
`ANTHROPIC_ENVIRONMENT_KEY=...` to `.env` (the example line is commented
out). Ask them to paste it there. Never echo it or log it.
5. **Start the sandbox side** with `./start.sh` and leave it running. It
builds the image first. Healthy output ends with `polling env=env_...`.
6. **Create a session from a second terminal** with the
`ant beta:sessions create ... --initial-event ...` command from that
demo's README (`docker-memory/` adds `--resource "{type: memory_store, ...}"`).
`set -a; . ./.env; set +a` first so the IDs are in scope.
The IDs come from `claude-lock.json` with the `jq` lines in that README,
and `archil/` wraps the same thing in `./fanout.sh`.
7. **Watch it run** in the `start.sh` terminal: `[on-work] session=sesn_...
(starting)`, then the container's own log (tool calls, and in the memory
demo `downloaded N memories ... -> /mnt/memory/...`), then
Expand All @@ -45,20 +54,24 @@ design. This file is the runbook.
8. **Memory demo, prove persistence**: after the first container exits,
create a second session asking what it remembers and confirm the recall
in `ant beta:sessions:events list --session-id ...`. Server side:
`ant beta:memory-stores:memories list --memory-store-id "$CLAUDE_MEMORY_STORE_ID" --view full`.
`ant beta:memory-stores:memories list --memory-store-id "$store" --view full`
(`$store` as read from `claude-lock.json` in that README).

## Debugging

| Symptom | Cause and fix |
|---|---|
| Session sits in `running`, container log shows `tool 'repl' not owned by this runner` and nothing else happens | The agent isn't pinned to `tools: [{type: agent_toolset_20260401}]`. The YAML in `agents/` pins it. A hand-made agent may not. (A pinned agent can still log the odd `repl` line and carry on: that's fine.) |
| Session sits in `running`, container log shows `tool 'repl' not owned by this runner` and nothing else happens | The agent isn't pinned to `tools: [{type: agent_toolset_20260401}]`. The file in `agents/` pins it. A hand-made agent may not. (A pinned agent can still log the odd `repl` line and carry on: that's fine.) |
| `on-work.sh` logs `carried no per-session secret` and exits 1 (memory demo) | The environment issued no per-session token, so a container would have no credential. Memory needs that token. Use `docker/` on that environment. |
| `sessions create --resource` returns 400 `resources are not supported with self-hosted environments` | The org doesn't have memory on self-hosted environments enabled yet. Nothing to fix locally. |
| Container log shows `failed to download skill` (memory demo) | Expected: the skills API takes the environment key, which this variant never puts in a container. See the README. |
| `start.sh` says `set ANTHROPIC_ENVIRONMENT_KEY` | Step 4 wasn't done: the key is neither in `.env` (uncommented) nor exported in that shell. |
| Poller gets 401 | The environment key doesn't belong to `CLAUDE_ENVIRONMENT_ID`, or `ANTHROPIC_BASE_URL` in `.env` points at a different API host than the one `setup.sh` used. |
| `start.sh` says `no environment ID` although `ant apply` ran and now reports `Everything is up to date` | `ant apply` was first run from another directory (the repo root, say), so `claude-lock.json` lives there and later runs walk up and adopt it. The scripts only read the lockfile beside them: move that `claude-lock.json` into the demo directory (its keys must read `./agents/...`, so if they carry a longer path, archive those resources with `ant apply --prune` from where it was created and apply again from the demo directory). |
| A user set up `docker/` or `docker-memory/` earlier with `agents/setup.sh` and has `CLAUDE_*_ID` lines in `.env` | `start.sh` still honors `CLAUDE_ENVIRONMENT_ID`, so their environment and key keep working. `ant apply` can't adopt those resources: either keep using them (read the agent and store IDs from `.env` for `sessions create`), or archive them in the Console and `ant apply .` fresh, then mint a key for the new environment. |
| Poller gets 401 | The environment key doesn't belong to the environment in `claude-lock.json`, or `ANTHROPIC_BASE_URL` in `.env` points at a different API host than the one `ant apply` used (the lockfile's `origin.base_url`). |
| `ant apply` prints `tracks resources on workspace X, but the credentials in use target Y` | `claude-lock.json` was written with different credentials (another `ant` profile or org). Re-run with that profile (`--profile`), or delete the lockfile to create fresh resources in the current workspace. |
| Container can't reach a service on the host (rootless Docker) | `SANDBOX_DOCKER_RUN_ARGS="--add-host=host.docker.internal:host-gateway" ./start.sh` and address the host as `host.docker.internal`. |
| Disk fills with `shs-ws-*` / `shs-mem-ws-*` volumes | Per-session `/workspace` volumes are never removed automatically. `docker volume rm` the dead sessions' ones. |

Both `start.sh` scripts deliberately `unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN`:
All three `start.sh` scripts deliberately `unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN`:
the sandbox host runs on the environment key alone. Don't "fix" that.
13 changes: 9 additions & 4 deletions managed-agents/self-hosted-sandboxes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Three demos of running managed-agent sessions on infrastructure you
control. All have the same shape: a self-hosted environment
(`config: {type: self_hosted}` in `agents/*/environment.yaml`) is a work
(`config: {type: self_hosted}` in `environments/self-hosted.yaml`) is a work
queue rather than a sandbox template, a host process polls it with the
environment key, and each claimed session runs in its own short-lived
sandbox. The first two use plain Docker containers on the host, the third
Expand Down Expand Up @@ -32,6 +32,11 @@ other's memories. One container per session is the recommended way to run
more than one session per host once memory is attached. The
`docker-memory/` README covers the mechanics.

In all three, `./agents/setup.sh` creates the resources from YAML with the `ant` CLI and
writes their IDs to `.env`. The one manual step is the environment key,
which you mint in the Console for the environment `setup.sh` created.
In all three, the resources are files: the agent under `agents/`, the
environment under `environments/`, and in `docker-memory/` the memory store
under `memory_stores/`.
[`ant apply .`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/apply)
creates them and records their IDs in `claude-lock.json`, which the scripts
read, and after you edit a file, running it again updates the same
resources. The one manual step is the environment key, which you mint in the
Console for the environment `ant apply` created.
13 changes: 7 additions & 6 deletions managed-agents/self-hosted-sandboxes/archil/.env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Auth for ./agents/setup.sh and for creating sessions (the control plane):
# either run `ant auth login` once, or set an API key here.
# Auth for the control plane (`ant apply`, `ant beta:sessions create`): run
# `ant auth login` once and leave this unset, or set an API key here and load
# it into your shell first (set -a; . ./.env; set +a), because the ant CLI does
# not read .env by itself.
# ANTHROPIC_API_KEY=

# The environment key for the self-hosted environment setup.sh creates. Mint
# it in the Console (Environments -> your environment -> Keys), then uncomment.
# The environment key for the self-hosted environment `ant apply` creates (its
# env_... ID is in the apply output and in claude-lock.json). Mint it in the
# Console (Environments -> your environment -> Keys), then uncomment.
# start.sh polls with it and on-work.py passes it into each sandbox.
# ANTHROPIC_ENVIRONMENT_KEY=

Expand All @@ -21,5 +24,3 @@ SEC_USER_AGENT="Your Name you@example.com"
# First quarter of the quarterly data sets to load (2021q1 for a ~15 GB,
# five-year load; the insider series starts at 2006q1, the full load is ~70 GB).
EDGAR_FROM=2006q1

# ./agents/setup.sh appends CLAUDE_ENVIRONMENT_ID and CLAUDE_AGENT_ID below.
4 changes: 4 additions & 0 deletions managed-agents/self-hosted-sandboxes/archil/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.env
__pycache__/
.pytest_cache/
# The IDs of the resources `ant apply` created for you. Ignored in this
# quickstarts repository because every reader applies to their own workspace.
# In a repository of your own, commit it (see the README).
claude-lock.json
25 changes: 18 additions & 7 deletions managed-agents/self-hosted-sandboxes/archil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ session idles.

## How to use it

Needs Python 3.10+ with the Archil SDK (`pip install -r requirements.txt`)
and the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart)
1.23 or later (`brew install anthropics/tap/ant`) with `ant auth login`. On
Needs Python 3.10+ with the Archil SDK (`pip install -r requirements.txt`),
`jq`, and the [`ant` CLI](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/quickstart)
1.30 or later (`brew install anthropics/tap/ant`) with `ant auth login`. On
the Archil side, in the [console](https://console.archil.com): a disk (use
its `dsk-...` ID), an **API key** from the API keys page, and a **Disk
Token** from the disk's page.
Expand All @@ -28,18 +28,29 @@ cd managed-agents/self-hosted-sandboxes/archil
claude "help me set up and run this Archil EDGAR demo"
```

Or by hand:
Or by hand, from this directory:

```sh
pip install -r requirements.txt
./agents/setup.sh # creates the self-hosted environment + agent, writes their IDs to .env
ant apply . # creates the self-hosted environment + agent, records their IDs in claude-lock.json
cp .env.example .env
# Fill in .env: ANTHROPIC_ENVIRONMENT_KEY (Console -> Environments -> Keys),
# ARCHIL_API_KEY, ARCHIL_REGION, ARCHIL_DISK, ARCHIL_MOUNT_TOKEN, SEC_USER_AGENT
set -a; . ./.env; set +a
python seed.py # loads the EDGAR data sets onto the disk (see below)
./start.sh # polls the environment with 3 workers
```

[`ant apply`](https://platform.claude.com/docs/en/cli-sdks-libraries/cli/apply)
reads the agent from `agents/edgar-analyst.md`, whose frontmatter is the
configuration and whose prose is the system prompt that describes the
tables, and the environment from `environments/self-hosted.yaml`. It shows
the plan and creates both once you approve. To retune the analyst, edit its
file and run `ant apply` again: it publishes a new version of the same
agent, because `claude-lock.json` remembers which resources these files
became. This repository ignores that file, since every reader creates their
own resources. In a project of your own, commit it.

From another terminal, start one analyst per subject, a company or a
person:

Expand Down Expand Up @@ -91,12 +102,12 @@ The SEC requires a `User-Agent` naming you on every download: set

| | |
|---|---|
| `agents/edgar-analyst/` | Agent and self-hosted environment definitions for `setup.sh`. The system prompt describes the tables, and the agent pins `tools: [{type: agent_toolset_20260401}]`, the toolset `ant beta:worker run` serves. |
| `agents/edgar-analyst.md`, `environments/self-hosted.yaml` | The agent and the self-hosted environment, as files for `ant apply`. The system prompt describes the tables, and the agent pins `tools: [{type: agent_toolset_20260401}]`, the toolset `ant beta:worker run` serves. |
| `sandboxes.py` | Three helpers over the [Archil Python SDK](https://pypi.org/project/archil/): create a sandbox, run a command to completion over the sandbox's process API (a websocket that streams stdout and stderr back while the command runs, reattaching if the connection drops), stop and delete. Also the bootstrap script that installs `ant` and `archil` in a fresh sandbox. |
| `seed.py` | One sandbox, exclusive mount, downloads and unpacks the three quarterly series and the two bulk indexes. |
| `on-work.py` | Once per claimed session: sandbox, `archil mount --shared`, `archil checkout reports/<session>`, `ant beta:worker run`, `checkin`, unmount, delete. |
| `start.sh` | Launches `WORKERS` pollers (default 3). One poller serves one session at a time, because the CLI stops a work item when `on-work.py` returns. |
| `fanout.sh` | One session per argument. |
| `fanout.sh` | One session per argument, on the agent and environment recorded in `claude-lock.json`. |

Sandboxes start from the stock `python:3.13` image and install the two
CLIs at boot (about 30s). Archil publishes its client through an install
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
# The agent. `ant apply` sends this frontmatter as the agent's configuration
# and the text below it as the system prompt, then records the agent's ID and
# version in claude-lock.json. Edit either part and run `ant apply` again to
# publish a new version of the same agent.
name: EDGAR analyst (Archil)
description: Maps the people and companies behind SEC insider filings, working directly on an Archil disk
model: claude-opus-5
metadata:
quickstart: archil
tools:
# Required, and it must be this toolset: it is the one `ant beta:worker
# run` serves from inside the sandbox. A server-default toolset includes
# tools the worker does not own, and the session stalls waiting on them.
- type: agent_toolset_20260401
---

You are a financial research analyst. Your working directory, /mnt/edgar,
is a shared Archil disk holding SEC EDGAR insider-transaction data sets:
Forms 3, 4, and 5, one directory per quarter under insider/<yyyyqN>/ (list
it first; the series can start anywhere from 2006q1), as tab-separated
tables joined on ACCESSION_NUMBER:

- SUBMISSION.tsv: the filing, with ISSUERCIK, ISSUERNAME, ISSUERTRADINGSYMBOL, FILING_DATE
- REPORTINGOWNER.tsv: the person or entity filing, with RPTOWNERCIK, RPTOWNERNAME,
RPTOWNER_RELATIONSHIP (Director, Officer, 10% owner, Other), RPTOWNER_TITLE
- NONDERIV_TRANS.tsv and DERIV_TRANS.tsv: shares bought or sold, with TRANS_DATE,
TRANS_CODE (P purchase, S sale, A award, M option exercise, G gift), TRANS_SHARES,
TRANS_PRICEPERSHARE, SHRS_OWND_FOLWNG_TRANS
- NONDERIV_HOLDING.tsv, DERIV_HOLDING.tsv, FOOTNOTES.tsv, OWNER_SIGNATURE.tsv
- company_tickers.json at the root maps tickers to CIKs.

Two more quarterly series sit beside it, same layout, one directory per
quarter:

- financials/<yyyyqN>/: the Financial Statement Data Sets, every number in
every 10-K and 10-Q. sub.txt (one row per filing: adsh, cik, name, form,
period), num.txt (adsh, tag, ddate, qtrs, value), pre.txt (which
statement and line each tag sits on), tag.txt (tag definitions). Join on
adsh. num.txt runs to hundreds of MB per quarter; filter by adsh or cik
before loading it anywhere.
- formd/<yyyyqN>/: Form D private-offering filings. ISSUERS.tsv (the
private company), RELATEDPERSONS.tsv (its executives, directors, and
promoters by name and role), OFFERING.tsv (amounts raised), joined on
ACCESSIONNUMBER. This is where private companies and their officers
appear.

Two bulk indexes sit beside it. submissions/CIK##########.json is a
company's complete EDGAR filing history (form types, dates, accession
numbers, primary document names; older filings continue in the
CIK##########-submissions-NNN.json files it names). companyfacts/
CIK##########.json holds every XBRL financial fact the company has
reported (revenue, net income, shares outstanding, by period). Any filing
you find there can be fetched from sec.gov with curl and a User-Agent
header, for example a proxy statement (DEF 14A) to read the board and
executive biographies, or an 8-K for an appointment or departure:
https://www.sec.gov/Archives/edgar/data/<cik>/<accession without dashes>/<primary document>

The data is large. Use ripgrep to find the CIK behind a name first, then
grep by CIK across quarters; CIKs are zero-padded to 10 digits in the
tables. sqlite3 is installed, and pip can install duckdb for bigger joins.
Names are inconsistently written; match on CIK whenever you can.

The disk is shared with other analysts and read-only except your own
directory, reports/<your session id>. Keep scratch files in /tmp and write
your final report to reports/<session id>/report.md. Cite accession numbers
for every claim, and say what the data cannot tell you.

For a person: find their reporting-owner CIK, then every company they have
filed against, their role and tenure at each, their trades over time, and
the people who filed alongside them (co-directors, co-officers) and where
else those people file. For a company: find its CIK (company_tickers.json, then SUBMISSION.tsv),
then its insiders over time: who joined and left when, their roles, who
bought and sold the most and when, and which of those insiders also file
at other companies (the other boards and executive teams they sit on).
A private company has no insider filings, but it may appear in formd/
with its related persons, and those people may file at public companies;
follow them there and say what the data cannot show. End your last message with the path of the report and a
five-line summary.
Loading