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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
6 changes: 0 additions & 6 deletions .gemini/.env

This file was deleted.

72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: ci

on:
push:
pull_request:

jobs:
python-and-shell:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install modal
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI installs modal without pinning or a requirements lock, which can introduce non-deterministic failures as dependencies evolve. Prefer installing from a pinned requirements file (e.g., requirements.txt/requirements-dev.txt) or pinning modal==... to keep CI reproducible.

Suggested change
python -m pip install modal
python -m pip install modal==0.72.39

Copilot uses AI. Check for mistakes.

- name: Install zsh
run: |
sudo apt-get update
sudo apt-get install -y zsh

- name: Run Python unit tests
run: python -m unittest discover -s tests -p 'test_*.py' -v

- name: Compile Python entrypoints
run: python -m compileall modal_tasks.py primary_compute.py scripts nim-claude-setup/server.py

- name: Validate bash scripts
run: |
find scripts -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
bash -n nim-claude-setup/nim-claude-proxy

- name: Validate zsh scripts
run: zsh -n nim-claude-setup/claude-nim.zsh

nestjs-fastify:
runs-on: ubuntu-latest
defaults:
run:
working-directory: nestjs-fastify-boilerplate
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: nestjs-fastify-boilerplate/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Build
run: npm run build

- name: Unit tests
run: npm test -- --runInBand

- name: E2E tests
run: npm run test:e2e -- --runInBand
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ __pycache__/
.venv/
.primary_compute_modal_usage.json
.modal-shims/
.gemini/.env
**/node_modules/
**/dist/
**/coverage/
**/*.tsbuildinfo
npm-debug.log*
220 changes: 49 additions & 171 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,227 +1,105 @@
# Modal Offload Toolkit

This repo now has two layers:
Modal Offload Toolkit is a small collection of utilities for pushing CPU-heavy work, generic CLI commands, and GitHub-only remote edits into Modal while keeping local developer machines thin.

1. `primary_compute.py`: step-by-step heavy compute offload with 6 cores / 14 GiB and daily budget tracking.
2. `modal_tasks.py` + shell shims: offload as many CLI commands as possible to Modal.
## What is inside

## 1) Setup once
- `primary_compute.py`: example Modal-backed heavy compute with daily budget tracking and local fallback.
- `modal_tasks.py`: generic command runner that snapshots repo changes and syncs them back from Modal.
- `scripts/modal_remote_tasks.py`: remote-only GitHub workflow helper that clones a repo in Modal, runs commands, optionally pushes to a branch, and can open a pull request.
- `scripts/*.sh`: shell shims and policy installers for routing terminal activity through Modal.
- `nestjs-fastify-boilerplate/`: Fastify-first NestJS starter with health and compute-planning endpoints.
- `nim-claude-setup/`: NVIDIA NIM bridge for Claude Code.

## Quick start

```bash
make setup
make auth
```

## 2) Heavy function in Modal

`primary_compute.py` defines:

- `heavy_task(payload)` running in Modal with:
- `cpu=6`
- `memory=14 * 1024` MiB
- `do_heavy_stuff(payload)` as the expensive CPU logic

The function resource config is pinned by:

```python
CPU_CORES = 6
MEMORY_MB = 14 * 1024
```

## 3) Modal usage budget tracking

Local state file:

- `~/.primary_compute_modal_usage.json`

Tracked values:
## Heavy compute in Modal

- `day` (YYYY-MM-DD)
- `used_min` (Modal runtime minutes measured locally)
`primary_compute.py` keeps expensive CPU work inside `do_heavy_stuff(...)` and exposes `run_heavy(...)` with three modes:

Default budget:
- `auto`: use Modal until the daily budget is exhausted.
- `modal`: always run remotely.
- `local`: run locally.

- `MAX_MIN_PER_DAY = 180` (3 hours/day)

Override with env var:

```bash
export PRIMARY_MODAL_MAX_MIN_PER_DAY=120
```

## 4) Run modes

Auto mode (recommended): Modal until daily budget is reached.
Examples:

```bash
make heavy PAYLOAD='{"iterations":24000000,"workers":6}'
```

Force Modal:

```bash
make heavy-modal PAYLOAD='{"iterations":24000000,"workers":6}'
```

Force local:

```bash
make heavy-local PAYLOAD='{"iterations":24000000,"workers":6}'
```

Show tracked usage:

```bash
make usage-show
```

Reset tracked usage:

```bash
make usage-reset
```

## 5) Payload knobs

- `iterations`: total compute loop count
- `workers`: parallel worker processes (`6` recommended for this setup)
- `salt`: checksum variation integer

Example:

```bash
make heavy PAYLOAD='{"iterations":30000000,"workers":6,"salt":23}'
```

## Notes

- This offloads CPU-heavy tasks well (backtests, pipelines, heavy notebook cells).
- macOS UI apps (Safari, Finder, TradingView, TWS UI) still run locally by design.
- Keep expensive logic inside `do_heavy_stuff(...)` and always call via `run_heavy(...)`.

## CLI Offload (any command that can run in Modal)

Run directly through the command gateway:

```bash
./scripts/modal_exec.sh -- <command> [args...]
./scripts/modal_exec.sh -c "<shell command>"
```
## CLI offload

Examples:
Send any shell command through the Modal task runner:

```bash
./scripts/modal_exec.sh -- rg "TODO" .
./scripts/modal_exec.sh -- python -m pytest -q
./scripts/modal_exec.sh --no-sync-back -- cat README.md
./scripts/modal_exec.sh -c "hostname && pwd"
```

Enable strict auto-routing shims:
Install the shell shims if you want repo-local command routing:

```bash
./scripts/install_modal_shims.sh
source ./scripts/activate_modal_only.sh
```

After activation, most executable commands are automatically forwarded to Modal.
Expected local-only exceptions are GUI/UI apps, shell builtins, `git`, and Modal control-plane commands.
## GitHub-only remote workflow

## Agent terminal-runner enforcement (Kilo, Gemini, Claude, Roo, Cline, Antigravity, Codex)

Install a global terminal runner once:
The global runner supports a fully remote branch-and-PR flow for cases where you do not want a local checkout to mutate:

```bash
make agent-runner-install
```

This creates:

```bash
$HOME/.local/bin/modal-agent-runner
```

Point each agent's terminal tool/command runner to that binary.
It forwards terminal commands to `scripts/modal_exec.sh`, so command execution is Modal-backed.
Model chat/inference stays on the agent provider side; this enforces terminal command execution only.

Command templates:

```bash
$HOME/.local/bin/modal-agent-runner -c "<raw command string>"
$HOME/.local/bin/modal-agent-runner -- <binary> <args...>
```

Quick verification:

```bash
make agent-runner-check
```

Remote-only mode (no local repo clone):

```bash
# created by make agent-runner-install
cat ~/.config/modal-agent-runner/config.env

# ensure Modal has github-token secret
/usr/bin/python3 -m modal secret create github-token GITHUB_TOKEN="$(gh auth token)" --force

# run from anywhere (for example /tmp)
cd /tmp
$HOME/.local/bin/modal-agent-runner -c "hostname && pwd"
```

In remote-only mode, the runner clones `MODAL_REMOTE_REPO_URL` inside Modal and executes there.

## Antigravity one-file policy

Canonical policy file:
Then configure `~/.config/modal-agent-runner/config.env`:

```bash
ANTIGRAVITY.md
MODAL_REMOTE_REPO_URL="https://github.com/Rohan5commit/Modal-terminal-instructions.git"
MODAL_REMOTE_REPO_BRANCH="main"
MODAL_REMOTE_PUSH="1"
MODAL_REMOTE_PUSH_BRANCH="codex/my-change"
MODAL_REMOTE_OPEN_PR="1"
MODAL_REMOTE_PR_BASE="main"
MODAL_REMOTE_PR_DRAFT="1"
MODAL_REMOTE_PR_TITLE="[modal] My remote change"
Comment on lines +67 to +74
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example config hard-codes a specific third-party repository URL, which is likely confusing/misleading for users of this repo. Recommend switching this to a placeholder (e.g., https://github.com/<org>/<repo>.git) or to this repository’s URL so the quickstart is self-contained.

Copilot uses AI. Check for mistakes.
```

Install workspace + Antigravity user-level policy wiring:
When `MODAL_REMOTE_PUSH_BRANCH` is set, the Modal runner pushes to that branch instead of directly updating the checkout branch. If `MODAL_REMOTE_OPEN_PR=1`, it will create or reuse a pull request against `MODAL_REMOTE_PR_BASE`.

```bash
make antigravity-policy-install
```
## NestJS Fastify starter

This installs:
The sample app is no longer a hello-world stub. It now includes:

- workspace `AGENTS.md` mirror from `ANTIGRAVITY.md`
- workspace `.clinerules` mirror from `ANTIGRAVITY.md` for Cline
- workspace `.rules/antigravity.rules` and `.rules/codex.rules` for Codex
- workspace `.vscode/settings.json` update for `geminicodeassist.rules` and `geminicodeassist.agentYoloMode=false`
- workspace Gemini hard-routing files:
- `.gemini/bin/bash` (wraps `bash -c ...` to `modal-agent-runner`)
- `.gemini/.env` (prepends `.gemini/bin` to `PATH`, sets `GEMINI_YOLO_MODE=false`)
- user files (when run outside Modal):
- `~/Library/Application Support/Antigravity/User/ANTIGRAVITY.md`
- `~/Library/Application Support/Antigravity/User/AGENTS.md` (symlink)
- `~/Library/Application Support/Antigravity/User/CLAUDE.md` (symlink)
- Antigravity settings updates for Kilo/Roo/Claude plus Gemini rules and `geminicodeassist.agentYoloMode=false`
- `GET /`: service overview and quick links.
- `GET /health`: health status, uptime, and timestamp.
- `GET /api/compute/presets`: sample payloads.
- `POST /api/compute/plan`: validated compute planning endpoint with workload partitioning, execution recommendation, and preview checksum.
- `GET /docs-json`: generated OpenAPI JSON.

Check policy + routing:
Run it locally from the subdirectory:

```bash
make antigravity-policy-check
cd nestjs-fastify-boilerplate
npm ci
npm run start:dev
```

## Shell hardening fix
## CI

Some agents execute commands in non-interactive `zsh` without sourcing repo scripts.
Install repo-scoped shell bootstrap once:
GitHub Actions now validates:

```bash
make shell-bootstrap
```
- Python unit checks and syntax compilation.
- Bash and zsh script parsing.
- NestJS lint, build, unit tests, and e2e tests.

This writes guarded blocks to `~/.zshenv`, `~/.zprofile`, `~/.zshrc`, `~/.bashrc`, and `~/.profile` so sessions that start in this repo automatically prepend `.modal-shims`.
For zsh, it also wraps common absolute binary paths (for example `/bin/ls` and `/usr/bin/python3`) to force Modal routing.

Run a verification for shell-level auto-routing:

```bash
make doctor
```
This keeps future remote-only changes from silently regressing the sample projects.
17 changes: 16 additions & 1 deletion modal_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,23 @@ def _ignore_local_path(path: Path) -> bool:
".mypy_cache",
".ruff_cache",
".venv",
".gemini",
".vscode",
".caches",
"node_modules",
"dist",
"coverage",
"build",
".next",
".turbo",
}
return any(part in ignored_parts for part in path.parts) or path.name in {".DS_Store"}
ignored_names = {".DS_Store", "npm-debug.log"}
ignored_suffixes = (".tsbuildinfo",)
return (
any(part in ignored_parts for part in path.parts)
or path.name in ignored_names
or any(path.name.endswith(suffix) for suffix in ignored_suffixes)
)


image = modal.Image.debian_slim()
Expand Down
Loading
Loading