{"slug": "phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack", "title": "PhD_fleet – Manage a virtual research lab of AI PhD students via Slack", "summary": "A new open-source Python toolkit called PhD_fleet allows researchers to manage a virtual lab of AI PhD students via Slack, using Claude Code agents for research tasks and a coach agent for mentoring feedback. The tool runs on any Python-capable host and connects to Slack via Socket Mode, enabling private subnet operation without inbound HTTP.", "body_md": "A small Python toolkit that lets one researcher — *the advisor* — spawn and\nconverse with a fleet of [Claude Code](https://github.com/anthropics/claude-code)\nagents through Slack. Each agent is its own Claude Code session in its own\nworkspace directory; Slack messages drive turns; between turns, the agent's\nfilesystem *is* its long-term memory.\n\nA separate **coach** agent watches how the advisor advises and gives\nevidence-based feedback on mentoring craft. The two intertwined goals: get real\nresearch done *and* grow as a mentor.\n\nThe host can be anything that runs Python and the Claude Code CLI — a laptop, a lab workstation, a cloud VM, an HPC login node. The bot connects to Slack over Socket Mode, so the host needs no inbound HTTP and works behind a NAT or in a private subnet.\n\n[What you get](#what-you-get)[Requirements](#requirements)[Quick start](#quick-start)[Setup in detail](#setup-in-detail)[Usage](#usage)[How it works](#how-it-works)[Configuration](#configuration)[Security model](#security-model)[Limitations](#limitations)[Project layout](#project-layout)[License](#license)\n\n**One Slack channel per agent.**`#student-<name>`\n\nfor each student you spawn;`#mentor-coach`\n\nfor the coach (created automatically at first startup).**Three slash commands.**`/new-student <name> <briefing>`\n\nscaffolds a workspace, creates the channel, and kicks off the first turn.`/coach-review <name> [days]`\n\nasks the coach to review*your*mentoring of a specific student.`/claude-status`\n\nprints a quick local readout — turns, last context size, cumulative tokens, model, cost, and GitHub link per agent. No Claude calls; just a registry view.\n\n**Per-agent journal.**`JOURNAL.md`\n\nin each agent's workspace is append-only, one section per turn, ending with a`Did / Found / Next`\n\nblock.**Shared paper library.**`library/`\n\nat the project root is a single pool every agent reads from and contributes to. First reader of a paper writes the canonical summary; later readers add a separate notes file.`library/README.md`\n\nis a regenerated index — agents only read it.**Per-turn commits to per-agent GitHub branches (optional).** If you configure an`origin`\n\n, after each turn the bot stages that agent's workspace into a fresh commit on`agent/<name>`\n\nand force-pushes it with a lease. The Slack final message links to the branch for review. With no`origin`\n\n, this step is skipped silently.**Quiet by default.** During a turn the bot posts at most one short*\"started\"*message and the agent's final reply — no per-tool stream of decorative messages. Failures and timeouts post a`:warning:`\n\nline.\n\n**Python 3.11 or newer.****The Claude Code CLI on** The bot shells out to it, so`PATH`\n\n.`claude --version`\n\nmust work in the same shell where you run the bot.**A Claude.ai subscription*** or*an Anthropic Console API key (see[Claude authentication](#4-claude-authentication)).**A Slack workspace**(free or paid) where you can install a custom app.*Optional:*a**GitHub repository** if you want per-turn review branches. The bot runs fine without one.\n\n```\ngit clone <this repo URL>\ncd phd_fleet\npython3 -m venv .venv\n.venv/bin/pip install -r requirements.txt\n\ncp .env.example .env          # then fill in the three required values\n\n.venv/bin/python bot.py\n```\n\nThen in Slack, run `/new-student alice \"your project briefing\"`\n\nto spawn your\nfirst agent. The rest of this section explains each step.\n\nSee the Quick start above. A virtualenv is recommended but not required — any\nenvironment with the dependencies in [requirements.txt](/canatara/phd_fleet/blob/main/requirements.txt) works.\n\nThe repo ships [slack-manifest.yaml](/canatara/phd_fleet/blob/main/slack-manifest.yaml) — a manifest that\nalready lists the three slash commands, the bot scopes, and the event\nsubscriptions. To use it:\n\n- Go to\n[https://api.slack.com/apps](https://api.slack.com/apps)→**Create New App**→** From a manifest**. - Pick your workspace, paste the contents of\n`slack-manifest.yaml`\n\n, and confirm. - Under\n**Basic Information → App-Level Tokens → Generate Token and Scopes**, create a token with the`connections:write`\n\nscope. Copy it (`xapp-…`\n\n) — that's your`SLACK_APP_TOKEN`\n\n. - Under\n**OAuth & Permissions**, install the app to your workspace. Copy the** Bot User OAuth Token**(`xoxb-…`\n\n) — that's your`SLACK_BOT_TOKEN`\n\n.\n\n```\ncp .env.example .env\n# edit .env and fill in SLACK_BOT_TOKEN, SLACK_APP_TOKEN, ADVISOR_SLACK_USER_ID\n```\n\nTo find your `ADVISOR_SLACK_USER_ID`\n\n: in Slack, click your profile picture →\n**View full profile** → the **…** menu → **Copy member ID**. It looks like\n`U0123ABC456`\n\n. The bot rejects messages from anyone whose user ID does not match\nthis value — the only access-control surface.\n\nTwo paths, in order of preference:\n\n**Subscription (recommended).** If`claude`\n\nis OAuth-authenticated to your Claude.ai account — i.e.,`~/.claude/.credentials.json`\n\nexists from running`claude /login`\n\nonce — the bot just uses it.**Leave**`ANTHROPIC_API_KEY`\n\nunset.**API key.** To pay per token through the Anthropic Console, set`ANTHROPIC_API_KEY=sk-ant-…`\n\nin`.env`\n\n. The Agent SDK picks it up and ignores the subscription path.\n\n```\n.venv/bin/python bot.py\n```\n\nYou should see:\n\n```\n… INFO ready — listening on Socket Mode (advisor=U0123ABC456)\n… INFO A new session (s_…) has been established\n… INFO ⚡️ Bolt app is running!\n```\n\nHealth check: in any channel where the bot is present, `@<bot> ping`\n\nreturns\n`pong`\n\n.\n\nFor long-running operation, run the bot under a process supervisor — `tmux`\n\nis\nthe simplest path, `systemd --user`\n\nif you want auto-restart on crash. The bot\nreconnects to Slack on transient disconnects but cannot survive a full process\nexit without a supervisor.\n\n```\n/new-student alice \"Investigate AlphaFold confidence on disordered regions. Read the recent literature, then propose a small experiment.\"\n```\n\nWhat this does:\n\n- Validates the name against\n`^[a-z0-9][a-z0-9_-]{0,40}$`\n\n. - Scaffolds\n`students/alice/`\n\nfrom`student_template/`\n\n, filling in the name and project briefing. - Creates\n`#student-alice`\n\nin Slack and invites you. - Registers Alice in\n`agents.json`\n\n(the runtime registry). - Kicks off the first turn — the agent reads its\n`CLAUDE.md`\n\n, gets oriented, and reports back.\n\nAfter that, every message you send in `#student-alice`\n\nbecomes the next prompt.\nThe agent's session resumes across turns and across bot restarts.\n\nThe coach has its own channel, `#mentor-coach`\n\n, created at first bot startup. Two\nways to use it:\n\n**Free chat.** Anything you write in`#mentor-coach`\n\nbecomes a prompt —*\"How should I handle a student proposing a method I think is wrong?\"*The coach responds in a coaching voice, asks clarifying questions, and names a relevant framework when appropriate.**Structured review.**`/coach-review alice 7`\n\npulls the last 7 days of`#student-alice`\n\nplus a recent excerpt of Alice's`JOURNAL.md`\n\n, and asks the coach to review*your*mentoring of Alice — what was done well, what could be sharper, each tied to a specific moment. The result is posted in`#mentor-coach`\n\n, and the coach also updates a longitudinal`mentor/coach/notes/advisees/alice.md`\n\n.\n\n```\n/claude-status\n```\n\nEphemeral reply listing every agent: kind, turns taken, last context size, cumulative input/output tokens, model, total cost, and the GitHub branch link if available. Pure local read — does not call Claude.\n\nManual on purpose. To see what you have, look in `students/`\n\n. To archive a\nstudent: `mv students/<name> students/_archived/`\n\nand remove its entry from\n`agents.json`\n\n. Slash commands for operations you'll do twice a year aren't worth\ntheir weight.\n\nEach student lives in `students/<name>/`\n\n:\n\n— persona and project briefing, filled in at create time from`CLAUDE.md`\n\n`student_template/`\n\n.— append-only research log, one section per session.`JOURNAL.md`\n\n— private scratchpads, design notes, intermediate analyses.`notes/`\n\n**the rest of the directory**— actual work artifacts (code, data, results).\n\nEach student is told to check `library/`\n\n(the shared pool) before reading anything\nnew, to write new paper summaries there, and never to run git — the bot handles\npublishing.\n\nThe lab-wide habits and conventions live in [LAB_CONTEXT.md](/canatara/phd_fleet/blob/main/LAB_CONTEXT.md),\nauto-appended to every agent's system prompt. Edit it once and every agent picks\nup the new rules on its next turn.\n\nThe coach lives in `mentor/coach/`\n\n, with the same workspace structure as a student\nplus `notes/advisees/<name>.md`\n\nfor longitudinal observations. Its `CLAUDE.md`\n\ncarries a coaching persona and a vocabulary of named frameworks (GROW; SBI;\nVygotsky's ZPD; feedforward). It uses the same runner, the same per-agent lock,\nand the same scaffolding as a student. The coach is reactive only: it speaks when\nsummoned — there is no auto-review after every student turn.\n\n`library/`\n\nis a single directory at the project root that every agent reads from\nand writes to:\n\n**First reader** of paper*X*writes`library/<citekey>.md`\n\n(a markdown summary with YAML frontmatter) and`library/<citekey>.pdf`\n\nif it's freely downloadable.**Later readers** who want to add their take write a*separate*file —`library/<citekey>__notes_<their-name>.md`\n\n. They never edit a peer's summary.is the index.`library/README.md`\n\n**Agents only read it.** The bot regenerates it after each turn by walking`library/*.md`\n\n, parsing frontmatter, and rewriting the table.\n\nCitekey collisions (two papers by the same author in the same year) are resolved\nwith letter suffixes — `jumper2021a`\n\n, `jumper2021b`\n\n. The full conventions live in\n[LAB_CONTEXT.md](/canatara/phd_fleet/blob/main/LAB_CONTEXT.md). This per-file-ownership shape avoids the failure\nmode where two agents append to the same index at once and clobber each other.\n\nIf you've configured `origin`\n\n, the bot publishes each agent's workspace to a\nper-agent branch (`agent/<name>`\n\n) after every turn, using a force-push with a\nlease. It stages into a temporary git index so the bot's commits never disturb\nyour working tree. The Slack final message links to the branch on GitHub. The\nstep-by-step rationale is documented in the comments of\n[src/agents.py](/canatara/phd_fleet/blob/main/src/agents.py) (`commit_and_push`\n\n).\n\nIf you don't want this, simply don't add an `origin`\n\n. The bot skips the publish\nstep silently and the Slack messages won't include review links.\n\nMost behavior is set in `.env`\n\n; the per-agent `.claude/settings.json`\n\nfiles carry\nthe permission deny-list (see [Security model](#security-model)). The `.env`\n\nknobs:\n\n| Variable | Default | Purpose |\n|---|---|---|\n`SLACK_BOT_TOKEN` |\nrequired | Bot User OAuth Token (`xoxb-…` ). |\n`SLACK_APP_TOKEN` |\nrequired | App-Level Token with `connections:write` (`xapp-…` ). |\n`ADVISOR_SLACK_USER_ID` |\nrequired | The single user allowed to talk to the bot. |\n`ANTHROPIC_API_KEY` |\nunset | Optional. Set to use a Console API key instead of subscription auth. |\n`AGENT_TURN_TIMEOUT_SECONDS` |\n`3600` |\nHow long one turn may run before being canceled. |\n\nThis is **defense-in-depth, not a sandbox.** Each agent's `.claude/settings.json`\n\ncarries a permission deny-list shipped in both templates:\n\n- Sensitive paths are never read:\n`**/.env*`\n\n,`**/.ssh/**`\n\n,`**/.aws/**`\n\n,`**/.config/gh/**`\n\n,`id_rsa*`\n\n,`id_ed25519*`\n\n,`/etc/**`\n\n,`/root/**`\n\n. - Privilege-escalation and cluster-job verbs are never run:\n`sudo`\n\n,`su`\n\n,`chmod`\n\n,`chown`\n\n,`srun`\n\n,`sbatch`\n\n,`scancel`\n\n,`salloc`\n\n.\n\nPer-deployment additions (e.g. other cluster schedulers) are easy to add to the\nsame file. The deny-list is not isolation: a determined prompt-injection from a\nfetched paper could still misuse `Bash`\n\nor `Write`\n\nwithin the agent's workspace.\n**Run the bot only on a host where that risk is acceptable**, and only ever as the\nsingle configured advisor — the bot drops every Slack message whose user ID\ndoesn't match `ADVISOR_SLACK_USER_ID`\n\n.\n\n**Single advisor.** No team / multi-advisor mode — the framework is shaped around one researcher's attention.**Reactive only.** Agents run when you message them. There is no scheduled wake-up, no inter-turn autonomy, no proactive coach observation.**No web UI.** Slack is the UI.**No automated test suite.** Verification is by smoke-running the bot.**Not a sandbox.** See[Security model](#security-model).\n\n```\nphd_fleet/\n├── bot.py                  # Slack entry point: handlers, bootstrap, main()\n├── src/\n│   ├── paths.py            # path constants, env config, logger, NAME_RE\n│   ├── agents.py           # registry, workspace scaffolding, git publishing\n│   ├── library.py          # LAB_CONTEXT loader + library/README.md index\n│   ├── slack_io.py         # Slack helpers, status, markdown → mrkdwn renderer\n│   └── runner.py           # run_agent — the per-turn Agent SDK driver\n├── requirements.txt\n├── pyproject.toml          # ruff / isort config\n├── slack-manifest.yaml     # paste into api.slack.com/apps to provision the app\n├── .env.example            # copy to .env and fill in the required values\n├── LAB_CONTEXT.md          # lab-wide rules, auto-appended to every agent\n├── student_template/       # scaffold copied into students/<name>/\n│   ├── CLAUDE.md\n│   ├── JOURNAL.md\n│   ├── notes/\n│   └── .claude/settings.json\n├── mentor_template/        # scaffold for the coach (created once at startup)\n│   ├── CLAUDE.md\n│   ├── JOURNAL.md\n│   ├── notes/advisees/\n│   └── .claude/settings.json\n├── library/                # shared paper pool\n│   └── README.md           # regenerated index; never edit by hand\n├── students/<name>/        # one workspace per student (created at runtime, gitignored)\n├── mentor/coach/           # the coach's workspace (created at startup, gitignored)\n└── agents.json             # runtime registry (created at runtime, gitignored)\n```\n\n`agents.json`\n\n, the virtualenv, and the per-agent workspaces under `students/`\n\nand\n`mentor/`\n\nare all gitignored. The intent: the repo tracks the *toolkit* — the bot,\nthe templates, the library scaffolding — while each agent's evolving work lives on\nits own GitHub branch via the per-turn publishing.\n\n[MIT](/canatara/phd_fleet/blob/main/LICENSE).", "url": "https://wpnews.pro/news/phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack", "canonical_source": "https://github.com/canatara/phd_fleet", "published_at": "2026-06-19 16:45:59+00:00", "updated_at": "2026-06-19 17:08:46.986716+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models"], "entities": ["PhD_fleet", "Claude Code", "Slack", "Anthropic", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack", "markdown": "https://wpnews.pro/news/phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack.md", "text": "https://wpnews.pro/news/phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack.txt", "jsonld": "https://wpnews.pro/news/phd-fleet-manage-a-virtual-research-lab-of-ai-phd-students-via-slack.jsonld"}}