docs: chat-driven project bootstrap design overview
Captures the architecture for going from "new project" chat command to a running, container-isolated, editor-accessible huskies project. Covers the three personas (chat-only / editor-using / multi-project), the container template (base + stack overlay + project bind mount), build sandbox model (host stays clean, all dep-code in container), editor-agnostic SSH access, git integration, and a 5-phase rollout. Source for upcoming bootstrap stories. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Chat-Driven Project Bootstrap
|
||||
|
||||
Design overview for going from "I want a new project" to a running,
|
||||
container-isolated, editor-accessible huskies project in one chat command.
|
||||
|
||||
## Goal
|
||||
|
||||
A user can say to Timmy in chat:
|
||||
|
||||
```
|
||||
new project myapp --stack rust
|
||||
new project legacy-rails --git git@github.com:me/legacy-rails.git
|
||||
```
|
||||
|
||||
and end up with:
|
||||
|
||||
1. A fresh docker container running the project's huskies node.
|
||||
2. The project's source code bind-mounted from the host so the user can
|
||||
edit it in any editor.
|
||||
3. SSH into the container so editors can run LSPs, builds, and tests
|
||||
inside the container — never on the host.
|
||||
4. Optional git remote configured for push to GitHub or Gitea.
|
||||
5. The new sled registered with the gateway, so Timmy can drive coders /
|
||||
mergemaster / etc. on the project via existing chat commands.
|
||||
|
||||
Manual repo creation on GitHub/Gitea remains the user's job. Everything
|
||||
downstream of that is orchestrated.
|
||||
|
||||
## Architecture at a Glance
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ Browser / Matrix │───┐
|
||||
└──────────────────────┘ │
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ Gateway (huskies-gw) │
|
||||
│ • chat dispatcher │
|
||||
│ • new-project │
|
||||
│ • routing │
|
||||
└─────────┬─────────────┘
|
||||
│
|
||||
┌─────────┴───────────────────────────────────┐
|
||||
│ docker engine (host) │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌─────────┐ │
|
||||
│ │ project-A │ │ project-B │ │ ... │ │
|
||||
│ │ sled + │ │ sled + │ │ │ │
|
||||
│ │ sshd + │ │ sshd + │ │ │ │
|
||||
│ │ LSPs │ │ LSPs │ │ │ │
|
||||
│ └─────┬──────┘ └─────┬──────┘ └─────────┘ │
|
||||
└────────┼──────────────┼─────────────────────┘
|
||||
│ │
|
||||
bind mount │ │ bind mount
|
||||
┌────────┴───┐ ┌─────┴──────┐
|
||||
│ ~/code/A │ │ ~/code/B │ ◄── host
|
||||
└────────────┘ └────────────┘ editor opens
|
||||
these paths
|
||||
```
|
||||
|
||||
- One container per project. The container runs the project's huskies
|
||||
binary (sled), an SSH server, and the stack-appropriate LSP(s).
|
||||
- Source lives on the host (e.g. `~/code/<project>`), bind-mounted into
|
||||
the container at a known path. Host can git-diff, back up, or edit.
|
||||
- The gateway is editor-agnostic and project-agnostic — it talks to each
|
||||
sled via the existing rendezvous / CRDT-sync protocol.
|
||||
|
||||
## Three Personas
|
||||
|
||||
| Persona | What they do | What they need |
|
||||
|---------|--------------|----------------|
|
||||
| Chat-only user | Drives everything via Matrix/web chat | Installed huskies binary; chat client |
|
||||
| Editor-using technical user | Same + edits source in their editor | SSH config to the container + editor-specific remote-dev setup |
|
||||
| Multi-project user | Several projects running in parallel | Gateway-listed projects, all routable from one chat |
|
||||
|
||||
Chat-only users never touch SSH. Editor users go through a one-time
|
||||
"copy this SSH command into your editor's remote settings" handoff at
|
||||
project creation time.
|
||||
|
||||
## The Bootstrap Chat Command
|
||||
|
||||
```
|
||||
new project <name> [--stack <stack>] [--git <url>] [--path <host-path>]
|
||||
```
|
||||
|
||||
Flow:
|
||||
|
||||
1. **Validate**: name unique among existing projects; host path doesn't already
|
||||
exist; stack (if declared) is one of the supported overlays.
|
||||
2. **Allocate** a fresh per-project port range (gateway picks).
|
||||
3. **Create host directory** at `--path` (default `~/huskies/<name>/`).
|
||||
4. If `--git` provided, `git clone` into that directory; else `git init`.
|
||||
5. **Detect stack** from cloned content if not declared:
|
||||
- `Cargo.toml` → `rust`
|
||||
- `package.json` → `node`
|
||||
- `go.mod` → `go`
|
||||
- `pyproject.toml` / `requirements.txt` / `setup.py` → `python`
|
||||
- `Gemfile` → `ruby`
|
||||
- `pom.xml` / `build.gradle` → `jvm`
|
||||
- Multiple → pick the dominant, warn.
|
||||
- None → minimal base image, user can install tooling later.
|
||||
6. **Compose the container** from `huskies-project-base` + the stack
|
||||
overlay (Dockerfile fragments under `docker/stacks/<stack>/`).
|
||||
7. **Launch** the container with bind mount + port forwards + an
|
||||
auto-generated SSH key.
|
||||
8. **Seed `.huskies/project.toml`** with sensible defaults.
|
||||
9. **Register** the project with the gateway (`gateway_projects` LWW-map).
|
||||
10. **Reply in chat** with: project name, host path, SSH command, and
|
||||
a `huskies status <name>` invocation to verify.
|
||||
|
||||
## Container Template
|
||||
|
||||
Layered:
|
||||
|
||||
- **`huskies-project-base`**: debian-slim + git + huskies binary + sshd
|
||||
+ sudo + a `huskies` user with the SSH pubkey installed.
|
||||
- **`huskies-stack-<stack>`**: per-stack additions. E.g. rust gets
|
||||
`rustup` + `rust-analyzer` + `cargo-nextest`; node gets `node@22` +
|
||||
`typescript-language-server`; etc.
|
||||
- **Project layer**: the bind-mounted `/workspace` is the project source,
|
||||
written by the host's editor, read by the in-container tooling.
|
||||
|
||||
The container's SSH server is bound to a host-local port (not exposed
|
||||
externally). Auth is the per-project keypair generated at bootstrap;
|
||||
the public key sits inside the container, the private key on host.
|
||||
|
||||
## Build Sandbox Model
|
||||
|
||||
The threat: editing code in a host-side editor causes the editor (or its
|
||||
LSP plugin) to run `cargo check` / `npm install` / `pip install` /
|
||||
similar, which executes arbitrary code from project dependencies —
|
||||
`build.rs`, proc-macros, npm `postinstall`, Python `setup.py`, Ruby
|
||||
native-extension build scripts, etc. A malicious dependency compromises
|
||||
the host.
|
||||
|
||||
The mitigation: all build / type-check / dependency-install commands
|
||||
execute **inside the project container**. The host's editor connects to
|
||||
the container over SSH; rust-analyzer (or equivalent) runs inside the
|
||||
container; the host process never `exec`s untrusted build scripts.
|
||||
|
||||
Container isolation is the docker default plus:
|
||||
- No `--privileged`.
|
||||
- No host bind mounts beyond the project source and the SSH key.
|
||||
- No host network beyond the gateway's CRDT sync port.
|
||||
- `--cap-drop=ALL` plus the minimum caps needed (probably none).
|
||||
|
||||
This isn't a hardened sandbox in the gvisor / Firecracker sense — a
|
||||
docker-escape exploit on a compromised container still escalates to
|
||||
host. For most consumer threat models (malicious crate from
|
||||
crates.io / npm), docker's default isolation is sufficient. Tighter
|
||||
sandboxing (gvisor) is a separate future spike if needed.
|
||||
|
||||
## Editor Connection — Editor-Agnostic SSH
|
||||
|
||||
| Editor | Connection mechanism |
|
||||
|--------|----------------------|
|
||||
| VSCode | Remote-SSH extension |
|
||||
| JetBrains (IntelliJ/Rover) | JetBrains Gateway (SSH) |
|
||||
| Zed | Built-in SSH remoting (mac/linux only today) |
|
||||
| Vim/Neovim | SSH terminal session, or local nvim + LSP-over-SSH |
|
||||
| Emacs | TRAMP + remote LSP via lsp-mode |
|
||||
|
||||
All converge on: `ssh huskies@127.0.0.1 -p <project-port> -i ~/.huskies/<name>/id_ed25519`.
|
||||
That string is emitted in the bootstrap chat reply.
|
||||
|
||||
## Git Integration
|
||||
|
||||
- Initial setup is `git init` or `git clone` inside the container.
|
||||
- For push: user's existing GitHub / Gitea SSH key is bind-mounted
|
||||
read-only into the container at `~/.ssh/id_*`, OR the user supplies a
|
||||
push token via `huskies secrets set GIT_TOKEN=...` (stored as a Fly
|
||||
secret equivalent — for now, a chmod 600 file in the container).
|
||||
- The container's `git` config gets `user.name` / `user.email` from the
|
||||
gateway-level user identity.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Alternative |
|
||||
|----------|--------|-------------|
|
||||
| Container per project | One container per project | One container many projects: simpler but breaks isolation, breaks per-project deps |
|
||||
| Editor model | SSH-remote (any editor) | VSCode Dev Containers only: simpler config but locks out everyone else |
|
||||
| Source location | Bind mount from host | Inside container only: breaks "I can also edit on my laptop" requirement |
|
||||
| Stack detection | Auto from project files, override with `--stack` | Always declared: more friction at bootstrap |
|
||||
| Push secrets | Bind-mounted host SSH key OR per-project token | Gateway holds tokens: bigger blast radius |
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Per-project resource limits.** Should each container have a hard
|
||||
CPU / RAM cap so a runaway agent doesn't starve the host?
|
||||
2. **Lifecycle / cleanup.** If the user deletes a project from chat,
|
||||
what gets removed? Container yes; host source no (data loss); git
|
||||
remotes yes? Need a confirm step.
|
||||
3. **Multi-tenant.** Out of scope for this design (that's huskies.dev
|
||||
territory). This doc assumes single-user local-only.
|
||||
4. **Windows specifics.** Bind mounts work but line-ending /
|
||||
permission edge cases. Probably document "use WSL2 for best
|
||||
experience" rather than fight Windows native paths.
|
||||
5. **Gateway-on-host vs gateway-in-container.** The gateway today runs
|
||||
in its own container. New per-project containers connect via docker
|
||||
network. Need to confirm the network plumbing works for arbitrary
|
||||
per-project containers, not just the manually-configured ones.
|
||||
|
||||
## Phasing
|
||||
|
||||
The work breaks naturally into:
|
||||
|
||||
- **Phase 0 (now):** this design doc.
|
||||
- **Phase 1:** chat command exists and provisions a bare project
|
||||
container (no stack overlay, no SSH, no git clone — just
|
||||
"start a container, register with gateway"). Validates the
|
||||
orchestration shell.
|
||||
- **Phase 2:** stack-aware container template — base image + overlays;
|
||||
detection from project files.
|
||||
- **Phase 3:** SSH-remote editor access — sshd in the container,
|
||||
per-project keypair, chat-reply emits the connection string.
|
||||
- **Phase 4:** git integration — `--git <url>` clones, host SSH key
|
||||
mount, push verification.
|
||||
- **Phase 5:** per-project resource limits + cleanup chat commands.
|
||||
|
||||
Each phase ships independently and is usable on its own. Phase 1 alone
|
||||
gives chat-only users a working project; later phases add the editor
|
||||
and git polish.
|
||||
Reference in New Issue
Block a user