agsekit 0.9.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. agsekit-0.9.4/LICENSE +21 -0
  2. agsekit-0.9.4/MANIFEST.in +4 -0
  3. agsekit-0.9.4/PKG-INFO +264 -0
  4. agsekit-0.9.4/README.md +229 -0
  5. agsekit-0.9.4/agsekit.egg-info/PKG-INFO +264 -0
  6. agsekit-0.9.4/agsekit.egg-info/SOURCES.txt +63 -0
  7. agsekit-0.9.4/agsekit.egg-info/dependency_links.txt +1 -0
  8. agsekit-0.9.4/agsekit.egg-info/entry_points.txt +2 -0
  9. agsekit-0.9.4/agsekit.egg-info/requires.txt +3 -0
  10. agsekit-0.9.4/agsekit.egg-info/top_level.txt +1 -0
  11. agsekit-0.9.4/agsekit_cli/__init__.py +7 -0
  12. agsekit-0.9.4/agsekit_cli/agent_scripts/claude-code.sh +12 -0
  13. agsekit-0.9.4/agsekit_cli/agent_scripts/codex-glibc.sh +212 -0
  14. agsekit-0.9.4/agsekit_cli/agent_scripts/codex.sh +83 -0
  15. agsekit-0.9.4/agsekit_cli/agent_scripts/proxychains_common.sh +105 -0
  16. agsekit-0.9.4/agsekit_cli/agent_scripts/qwen.sh +83 -0
  17. agsekit-0.9.4/agsekit_cli/agents.py +271 -0
  18. agsekit-0.9.4/agsekit_cli/backup.py +294 -0
  19. agsekit-0.9.4/agsekit_cli/cli.py +166 -0
  20. agsekit-0.9.4/agsekit_cli/commands/__init__.py +15 -0
  21. agsekit-0.9.4/agsekit_cli/commands/backup_once.py +31 -0
  22. agsekit-0.9.4/agsekit_cli/commands/backup_repeated.py +148 -0
  23. agsekit-0.9.4/agsekit_cli/commands/config_example.py +42 -0
  24. agsekit-0.9.4/agsekit_cli/commands/config_gen.py +210 -0
  25. agsekit-0.9.4/agsekit_cli/commands/create_vm.py +83 -0
  26. agsekit-0.9.4/agsekit_cli/commands/destroy_vm.py +112 -0
  27. agsekit-0.9.4/agsekit_cli/commands/install_agents.py +208 -0
  28. agsekit-0.9.4/agsekit_cli/commands/mounts.py +122 -0
  29. agsekit-0.9.4/agsekit_cli/commands/portforward.py +141 -0
  30. agsekit-0.9.4/agsekit_cli/commands/prepare.py +260 -0
  31. agsekit-0.9.4/agsekit_cli/commands/run.py +171 -0
  32. agsekit-0.9.4/agsekit_cli/commands/shell.py +72 -0
  33. agsekit-0.9.4/agsekit_cli/commands/ssh.py +119 -0
  34. agsekit-0.9.4/agsekit_cli/commands/start_vm.py +83 -0
  35. agsekit-0.9.4/agsekit_cli/commands/stop.py +78 -0
  36. agsekit-0.9.4/agsekit_cli/commands/systemd.py +108 -0
  37. agsekit-0.9.4/agsekit_cli/config.py +370 -0
  38. agsekit-0.9.4/agsekit_cli/i18n.py +67 -0
  39. agsekit-0.9.4/agsekit_cli/interactive.py +487 -0
  40. agsekit-0.9.4/agsekit_cli/locales/en.json +344 -0
  41. agsekit-0.9.4/agsekit_cli/locales/ru.json +344 -0
  42. agsekit-0.9.4/agsekit_cli/mounts.py +60 -0
  43. agsekit-0.9.4/agsekit_cli/run_with_proxychains.sh +47 -0
  44. agsekit-0.9.4/agsekit_cli/vm.py +405 -0
  45. agsekit-0.9.4/config-example.yaml +34 -0
  46. agsekit-0.9.4/pyproject.toml +31 -0
  47. agsekit-0.9.4/setup.cfg +4 -0
  48. agsekit-0.9.4/tests/test_agents.py +154 -0
  49. agsekit-0.9.4/tests/test_backup_progress.py +96 -0
  50. agsekit-0.9.4/tests/test_backup_repeated.py +74 -0
  51. agsekit-0.9.4/tests/test_backup_repeated_commands.py +245 -0
  52. agsekit-0.9.4/tests/test_config_agents.py +44 -0
  53. agsekit-0.9.4/tests/test_config_gen_command.py +92 -0
  54. agsekit-0.9.4/tests/test_config_mounts.py +59 -0
  55. agsekit-0.9.4/tests/test_config_vms.py +120 -0
  56. agsekit-0.9.4/tests/test_create_vm_command.py +48 -0
  57. agsekit-0.9.4/tests/test_install_agents_command.py +81 -0
  58. agsekit-0.9.4/tests/test_interactive_mode.py +244 -0
  59. agsekit-0.9.4/tests/test_make_single_backup.py +400 -0
  60. agsekit-0.9.4/tests/test_mount_commands.py +191 -0
  61. agsekit-0.9.4/tests/test_prepare_command.py +93 -0
  62. agsekit-0.9.4/tests/test_run_command.py +253 -0
  63. agsekit-0.9.4/tests/test_shell_command.py +133 -0
  64. agsekit-0.9.4/tests/test_start_vm_command.py +108 -0
  65. agsekit-0.9.4/tests/test_stop_vm_command.py +108 -0
agsekit-0.9.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mihanentalpo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include config-example.yaml
2
+ recursive-include agsekit_cli/agent_scripts *.sh
3
+ include agsekit_cli/run_with_proxychains.sh
4
+ recursive-include agsekit_cli/locales *.json
agsekit-0.9.4/PKG-INFO ADDED
@@ -0,0 +1,264 @@
1
+ Metadata-Version: 2.4
2
+ Name: agsekit
3
+ Version: 0.9.4
4
+ Summary: Agent Safety Kit command-line utilities
5
+ Author-email: Mihanentalpo <mihanentalpo@yandex.ru>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Mihanentalpo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: click<9,>=8.1
32
+ Requires-Dist: PyYAML<7,>=6.0
33
+ Requires-Dist: questionary<3,>=2.0
34
+ Dynamic: license-file
35
+
36
+ [README.md на русском](README-ru.md)
37
+
38
+ # Agent Safety Kit
39
+
40
+ A toolkit for running AI agents in an isolated environment inside a Multipass virtual machine.
41
+
42
+ ## Why this matters
43
+
44
+ <img width="437" height="379" alt="image" src="https://github.com/user-attachments/assets/c3486072-e96a-4197-8b1f-d6ac228c2cc6" />
45
+
46
+ Some stories (you can find plenty more):
47
+
48
+ * [Qwen Coder agent destroys working builds](https://github.com/QwenLM/qwen-code/issues/354)
49
+ * [Codex keeps deleting unrelated and uncommitted files! even ignoring rejected requests](https://github.com/openai/codex/issues/4969)
50
+ * [comment: qwen-code CLI destroyed my entire project, deleted important files](https://www.reddit.com/r/DeepSeek/comments/1mmfjsl/right_now_qwen_is_the_best_model_they_have_the/)
51
+ * [Claude Code deleted my entire workspace, here's the proof](https://www.reddit.com/r/ClaudeAI/comments/1m299f5/claude_code_deleted_my_entire_workspace_heres_the/)
52
+ * [I Asked Claude Code to Fix All Bugs, and It Deleted the Whole Repo](https://levelup.gitconnected.com/i-asked-claude-code-to-fix-all-bugs-and-it-deleted-the-whole-repo-e7f24f5390c5)
53
+ * [Codex has twice deleted and corrupted my files (r/ClaudeAI comment)](https://www.reddit.com/r/ClaudeAI/comments/1nhvyu0/openai_drops_gpt5_codex_cli_right_after/)
54
+
55
+ Everyone says "you should have backups" and "everything must live in git", but console AI agents still lack built-in snapshots to roll back after every change they make. Until sandboxes catch up, this toolkit helps you manage that yourself.
56
+
57
+ ## Key ideas
58
+
59
+ - Agents run only inside a virtual machine.
60
+ - The VM is launched via Multipass (a simple Canonical tool to start Ubuntu VMs with a single command).
61
+ - Project folders from the host are mounted into the VM; an automatic backup job runs in parallel to a sibling directory at a configurable interval (defaults to every five minutes and only when changes are detected), using `rsync` with hardlinks to save space.
62
+ - VM, mount, and cloud-init settings are stored in a YAML config.
63
+ - You can run the agent without entering the guest via `multipass shell`—it still executes inside the VM.
64
+ - Multipass commands and agent runs can be wrapped in proxychains: set a proxy URL per VM or override it once with `--proxychains` to generate a temporary proxychains config automatically.
65
+
66
+ ## Working agents
67
+
68
+ Currently confirmed working agent types are:
69
+
70
+ - qwen
71
+ - codex
72
+ - codex-glibc (built dynamically)
73
+
74
+ ## Quick start
75
+
76
+ 1. Install the package with pip (requires Python 3.9 or newer):
77
+ ```bash
78
+ python3 -m venv ./venv
79
+ source ./venv/bin/activate
80
+ pip install agsekit
81
+ ```
82
+ This makes the `agsekit` command available inside the virtual environment.
83
+
84
+ 2. Alternatively, clone the repository and install from sources:
85
+ ```bash
86
+ git clone https://github.com/MihanEntalpo/agent-safety-kit/
87
+ cd agent-safety-kit
88
+ pip install .
89
+ ```
90
+
91
+ 3. Create a YAML configuration (the CLI checks `--config`, then `CONFIG_PATH`, then `~/.config/agsekit/config.yaml`):
92
+ ```bash
93
+ agsekit config-example
94
+ # edit vms/mounts/cloud-init to your needs
95
+ ```
96
+ When working from a cloned repository, you can also copy the file directly:
97
+ ```bash
98
+ mkdir -p ~/.config/agsekit
99
+ cp config-example.yaml ~/.config/agsekit/config.yaml
100
+ ```
101
+ You can also run `agsekit config-gen` to answer a few questions and save the config (defaults to `~/.config/agsekit/config.yaml`; use `--overwrite` to replace an existing file).
102
+
103
+ 4. Install required system dependencies (in particular, Multipass; requires sudo and currently works only on Debian-based systems):
104
+ ```bash
105
+ agsekit prepare
106
+ ```
107
+
108
+ 5. Create the virtual machines defined in YAML:
109
+ ```bash
110
+ agsekit create-vms
111
+ ```
112
+
113
+ To launch just one VM, use `agsekit create-vm <name>`. If the config contains only one VM, you can omit `<name>` and it will be used automatically. If a VM already exists, the command compares the desired resources with the current ones and reports any differences. Changing resources of an existing VM is not supported yet.
114
+
115
+ 6. Mount your folders (assuming mounts are already configured in the YAML file):
116
+ ```bash
117
+ agsekit mount --all
118
+ ```
119
+
120
+ 7. Install all configured agents into their default VMs:
121
+ ```bash
122
+ agsekit install-agents --all-agents
123
+ ```
124
+
125
+ 8. Launch an agent inside its VM (example runs `qwen` in the folder where `/host/path/project` is mounted, with backups enabled by default):
126
+ ```bash
127
+ agsekit run qwen /host/path/project --vm agent-ubuntu
128
+ ```
129
+ On the very first run with backups enabled, the CLI creates an initial snapshot with progress output before launching the agent, so wait for it to complete.
130
+
131
+ ## agsekit commands
132
+
133
+ ### Setup and VM lifecycle
134
+
135
+ * `agsekit prepare` — installs required system dependencies (including Multipass; requires sudo and currently works only on Debian-based systems).
136
+ * `agsekit config-gen [--config <path>] [--overwrite]` — interactive wizard that asks about VMs, mounts, and agents, then writes a YAML config to the chosen path (defaults to `~/.config/agsekit/config.yaml`). Without `--overwrite`, the command warns if the file already exists.
137
+ * `agsekit config-example [<path>]` — copies `config-example.yaml` to the target path (defaults to `~/.config/agsekit/config.yaml`). If the default config already exists, the command skips copying.
138
+ * `agsekit create-vms` — creates every VM defined in the YAML configuration.
139
+ * `agsekit create-vm <name>` — launches just one VM. If the config contains only one VM, you can omit `<name>` and it will be used automatically. If a VM already exists, the command compares the desired resources with the current ones and reports any differences. Changing resources of an existing VM is not supported yet.
140
+ * `agsekit shell [<vm_name>] [--config <path>]` — opens an interactive `multipass shell` session inside the chosen VM, applying any configured port forwarding. If only
141
+ one VM is defined in the config, the CLI connects there even without `vm_name`. When multiple VMs exist and the command runs in
142
+ a TTY, the CLI prompts you to pick one; in non-interactive mode, an explicit `vm_name` is required.
143
+ * `agsekit ssh <vm_name> [--config <path>] [<ssh_args...>]` — connects to the VM over SSH using `~/.config/agsekit/ssh/id_rsa` and forwards any extra arguments directly to the `ssh` command (for example, `-L`, `-R`, `-N`).
144
+ * `agsekit portforward [--config <path>]` — starts a dedicated `agsekit ssh` tunnel for each VM that defines `port-forwarding` rules, monitoring the child processes and restarting them if they exit. Stop with Ctrl+C to gracefully terminate the tunnels.
145
+ * `agsekit start-vm <vm_name> [--config <path>]` — starts the specified VM from the configuration. If only one VM is configured, the name can be omitted.
146
+ * `agsekit start-vm --all-vms [--config <path>]` — starts every VM declared in the config file.
147
+ * `agsekit stop-vm <vm_name> [--config <path>]` — stops the specified VM from the configuration. If only one VM is configured, the name can be omitted.
148
+ * `agsekit stop-vm --all-vms [--config <path>]` — stops every VM declared in the config file.
149
+ * `agsekit destroy-vm <vm_name> [--config <path>] [-y]` — deletes the specified VM from Multipass. Without `-y`, the CLI asks for interactive confirmation.
150
+ * `agsekit destroy-vm --all [--config <path>] [-y]` — deletes every VM from the configuration, with the same confirmation requirement.
151
+ * `agsekit systemd install [--config <path>]` — writes `~/.config/agsekit/systemd.env` with absolute paths to `agsekit`, the config, and the current project directory, then registers and starts the user unit from `systemd/agsekit-portforward.service` via `systemctl --user` (link, daemon-reload, start, enable).
152
+ * `agsekit systemd uninstall` — stops and disables the user unit, then removes the linked `systemd/agsekit-portforward.service` from systemd via `systemctl --user`.
153
+
154
+ ### Mount management
155
+
156
+ * `agsekit mount --source-dir <path> [--config <path>]` — mounts the directory described by `source` in the configuration file (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) into its VM using `multipass mount`. Use `--all` to mount every entry from the config. When there is only one mount in the config, the command can be run without `--source-dir` or `--all`.
157
+ * `agsekit umount --source-dir <path> [--config <path>]` — unmounts the directory described by `source` in the config (or `CONFIG_PATH`/`--config`); `--all` unmounts every configured path. If only one mount is configured, the command will unmount it even without explicit flags.
158
+
159
+ ### Backups
160
+
161
+ #### One-off backup
162
+
163
+ `agsekit backup-once --source-dir <path> --dest-dir <path> [--exclude <pattern> ...] [--progress]` — runs a single backup of the source directory into the specified destination using `rsync`.
164
+ The command creates a timestamped directory with a `-partial` suffix, supports incremental copies via `--link-dest` to the previous backup, and honors exclusions from `.backupignore` and `--exclude` arguments. When finished, the temporary folder is renamed to a final timestamp without the suffix. If nothing changed relative to the last backup, no new snapshot is created and the tool reports the absence of updates.
165
+ Pass `--progress` to forward rsync progress flags and show a console progress bar while files are copied.
166
+
167
+ `.backupignore` examples:
168
+ ```
169
+ # exclude virtual environments and dependencies
170
+ venv/
171
+ node_modules/
172
+
173
+ # ignore temporary and log files by pattern
174
+ *.log
175
+ *.tmp
176
+
177
+ # include a specific file inside an excluded folder
178
+ !logs/important.log
179
+
180
+ # skip documentation build artifacts
181
+ docs/build/
182
+ ```
183
+
184
+ Backups use `rsync` with incremental links (`--link-dest`) to the previous copy: if only a small set of files changed, the new snapshot stores just the updated data, while unchanged files are hardlinked to the prior snapshot. This keeps a chain of dated directories while consuming minimal space when changes are rare.
185
+
186
+ #### Repeated backups
187
+
188
+ * `agsekit backup-repeated --source-dir <path> --dest-dir <path> [--exclude <pattern> ...] [--interval <minutes>] [--skip-first]` — runs an immediate backup and then repeats it every `interval` minutes (defaults to five minutes). With `--skip-first`, the loop waits for the first interval before performing the initial run. After each backup it prints `Done, waiting N minutes` with the actual interval value.
189
+ * `agsekit backup-repeated-mount --mount <path> [--config <path>]` — looks up the mount by its `source` path in the configuration file (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) and launches repeated backups using the paths and interval from the config. When only one mount is present, `--mount` can be omitted; with multiple mounts, an explicit choice is required.
190
+ * `agsekit backup-repeated-all [--config <path>]` — reads all mounts from the config (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) and starts concurrent repeated backups for each entry within a single process. Use Ctrl+C to stop the loops.
191
+
192
+ ### Agent installation
193
+
194
+ * `agsekit install-agents <agent_name> [<vm>|--all-vms] [--config <path>] [--proxychains <value>]` — runs the prepared installation script for the chosen agent type inside the specified VM (or the agent's default VM if none is provided). If the config defines only one agent, you can skip `<agent_name>` and it will be picked automatically. Use `--proxychains <scheme://host:port>` to override the VM proxy for this installation or `--proxychains ""` to ignore it once.
195
+ * `agsekit install-agents --all-agents [--all-vms] [--config <path>] [--proxychains <value>]` — installs every configured agent either into their default VM or into every VM when `--all-vms` is set.
196
+
197
+ The installation scripts live in `agsekit_cli/agent_scripts/`: `codex` installs the npm CLI, `codex-glibc` builds the Rust sources with the glibc target and installs the binary as `codex-glibc`, and `qwen`/`claude-code` follow their upstream steps (the `qwen` script installs the qwen-code CLI). Other agent types are not supported yet.
198
+
199
+ ### Running agents
200
+
201
+ * `agsekit run <agent_name> [<source_dir>|--vm <vm_name>] [--config <path>] [--proxychains <value>] [--disable-backups] [--skip-default-args] [--debug] -- <agent_args...>` — starts an interactive agent command inside Multipass. Environment variables from the config are passed to the process. If a `source_dir` from the mounts list is provided, the agent starts inside the mounted target path in the matching VM; otherwise it launches in the home directory of the default VM. Unless `--disable-backups` is set, background repeated backups for the selected mount are started for the duration of the run. When no backups exist yet, the CLI first creates an initial snapshot with progress output before launching the agent and then starts the repeated loop with the initial run skipped. Arguments from `agents.<name>.default-args` are added unless `--skip-default-args` is set; if the user already passed an option with the same name (for example `--openai-api-key`), the default value is skipped. With `--debug`, the CLI prints every external command before executing it to help troubleshoot agent launches. Use `--proxychains <scheme://host:port>` to override the VM setting for one run; pass an empty string to disable it temporarily.
202
+
203
+ ### Interactive mode
204
+
205
+ In a TTY you don’t have to type full commands every time: the CLI can guide you through an interactive menu that fills in parameters for you.
206
+
207
+ * Run `agsekit` without arguments to open the interactive menu, choose a command, and select options such as the config path, mounts, or agent parameters.
208
+ * Start a command without mandatory arguments (for example, `agsekit run`) to automatically fall back to the interactive flow after the CLI prints a “not enough parameters” hint. Use `--non-interactive` if you prefer the usual help output instead of prompts.
209
+
210
+ ## Localization
211
+
212
+ The CLI reads the system locale and falls back to English if it cannot detect a supported language. You can override this behavior with the `AGSEKIT_LANG` environment variable:
213
+
214
+ ```bash
215
+ AGSEKIT_LANG=ru agsekit --help
216
+ ```
217
+
218
+ ## YAML configuration
219
+
220
+ The configuration file (looked up via `--config`, `CONFIG_PATH`, or `~/.config/agsekit/config.yaml`) describes VM parameters, mounted directories, and any `cloud-init` settings. A base example lives in `config-example.yaml`:
221
+
222
+ ```yaml
223
+ vms: # VM parameters for Multipass (you can define multiple)
224
+ agent-ubuntu: # VM name
225
+ cpu: 2 # number of vCPUs
226
+ ram: 4G # RAM size (supports 2G, 4096M, etc.)
227
+ disk: 20G # disk size
228
+ proxychains: "" # optional proxy URL (scheme://host:port); agsekit writes a temporary proxychains.conf and wraps Multipass commands automatically
229
+ cloud-init: {} # place your standard cloud-init config here if needed
230
+ port-forwarding: # Port forwarding config
231
+ - type: remote # Open port inside VM and pass connections to Host machine's port
232
+ host-addr: 127.0.0.1:80
233
+ vm-addr: 127.0.0.1:8080
234
+ - type: local # Open port on Host machine, and pass connections to VM's port
235
+ host-addr: 0.0.0.0:15432
236
+ vm-addr: 127.0.0.1:5432
237
+ - type: socks5 # Open socks5-proxy port inside VM, directing traffic to Host machine's network
238
+ vm-addr: 127.0.0.1:8088
239
+ mounts:
240
+ - source: /host/path/project # path to the source folder on the host
241
+ target: /home/ubuntu/project # mount point inside the VM; defaults to /home/ubuntu/<source_basename>
242
+ backup: /host/backups/project # backup directory; defaults to backups-<source_basename> next to source
243
+ interval: 5 # backup interval in minutes; defaults to 5 if omitted
244
+ vm: agent-ubuntu # VM name; defaults to the first VM in the configuration
245
+ agents:
246
+ qwen: # agent name; add as many as you need
247
+ type: qwen # agent type: qwen (installs and uses the `qwen` binary), codex, codex-glibc (installs the `codex-glibc` binary), or claude-code (other types are not supported yet)
248
+ env: # arbitrary environment variables passed to the agent process
249
+ OPENAI_API_KEY: "my_local_key"
250
+ OPENAI_BASE_URL: "https://127.0.0.1:11556/v1"
251
+ OPENAI_MODEL: "Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8"
252
+ default-args: # arguments passed to the agent unless the user overrides them
253
+ - "--openai-api-key=my_local_key"
254
+ - "--openai-base-url=https://127.0.0.1:11556/v1"
255
+ vm: qwen-ubuntu # default VM for this agent; falls back to the mount VM or the first VM in the list
256
+ codex:
257
+ type: codex
258
+ claude:
259
+ type: claude-code
260
+ codex2:
261
+ type: codex-glibc
262
+ ```
263
+
264
+ > **Note:** Prefer ASCII-only paths for both `source` and `target` mount points: AppArmor may refuse to mount directories whose paths contain non-ASCII characters.
@@ -0,0 +1,229 @@
1
+ [README.md на русском](README-ru.md)
2
+
3
+ # Agent Safety Kit
4
+
5
+ A toolkit for running AI agents in an isolated environment inside a Multipass virtual machine.
6
+
7
+ ## Why this matters
8
+
9
+ <img width="437" height="379" alt="image" src="https://github.com/user-attachments/assets/c3486072-e96a-4197-8b1f-d6ac228c2cc6" />
10
+
11
+ Some stories (you can find plenty more):
12
+
13
+ * [Qwen Coder agent destroys working builds](https://github.com/QwenLM/qwen-code/issues/354)
14
+ * [Codex keeps deleting unrelated and uncommitted files! even ignoring rejected requests](https://github.com/openai/codex/issues/4969)
15
+ * [comment: qwen-code CLI destroyed my entire project, deleted important files](https://www.reddit.com/r/DeepSeek/comments/1mmfjsl/right_now_qwen_is_the_best_model_they_have_the/)
16
+ * [Claude Code deleted my entire workspace, here's the proof](https://www.reddit.com/r/ClaudeAI/comments/1m299f5/claude_code_deleted_my_entire_workspace_heres_the/)
17
+ * [I Asked Claude Code to Fix All Bugs, and It Deleted the Whole Repo](https://levelup.gitconnected.com/i-asked-claude-code-to-fix-all-bugs-and-it-deleted-the-whole-repo-e7f24f5390c5)
18
+ * [Codex has twice deleted and corrupted my files (r/ClaudeAI comment)](https://www.reddit.com/r/ClaudeAI/comments/1nhvyu0/openai_drops_gpt5_codex_cli_right_after/)
19
+
20
+ Everyone says "you should have backups" and "everything must live in git", but console AI agents still lack built-in snapshots to roll back after every change they make. Until sandboxes catch up, this toolkit helps you manage that yourself.
21
+
22
+ ## Key ideas
23
+
24
+ - Agents run only inside a virtual machine.
25
+ - The VM is launched via Multipass (a simple Canonical tool to start Ubuntu VMs with a single command).
26
+ - Project folders from the host are mounted into the VM; an automatic backup job runs in parallel to a sibling directory at a configurable interval (defaults to every five minutes and only when changes are detected), using `rsync` with hardlinks to save space.
27
+ - VM, mount, and cloud-init settings are stored in a YAML config.
28
+ - You can run the agent without entering the guest via `multipass shell`—it still executes inside the VM.
29
+ - Multipass commands and agent runs can be wrapped in proxychains: set a proxy URL per VM or override it once with `--proxychains` to generate a temporary proxychains config automatically.
30
+
31
+ ## Working agents
32
+
33
+ Currently confirmed working agent types are:
34
+
35
+ - qwen
36
+ - codex
37
+ - codex-glibc (built dynamically)
38
+
39
+ ## Quick start
40
+
41
+ 1. Install the package with pip (requires Python 3.9 or newer):
42
+ ```bash
43
+ python3 -m venv ./venv
44
+ source ./venv/bin/activate
45
+ pip install agsekit
46
+ ```
47
+ This makes the `agsekit` command available inside the virtual environment.
48
+
49
+ 2. Alternatively, clone the repository and install from sources:
50
+ ```bash
51
+ git clone https://github.com/MihanEntalpo/agent-safety-kit/
52
+ cd agent-safety-kit
53
+ pip install .
54
+ ```
55
+
56
+ 3. Create a YAML configuration (the CLI checks `--config`, then `CONFIG_PATH`, then `~/.config/agsekit/config.yaml`):
57
+ ```bash
58
+ agsekit config-example
59
+ # edit vms/mounts/cloud-init to your needs
60
+ ```
61
+ When working from a cloned repository, you can also copy the file directly:
62
+ ```bash
63
+ mkdir -p ~/.config/agsekit
64
+ cp config-example.yaml ~/.config/agsekit/config.yaml
65
+ ```
66
+ You can also run `agsekit config-gen` to answer a few questions and save the config (defaults to `~/.config/agsekit/config.yaml`; use `--overwrite` to replace an existing file).
67
+
68
+ 4. Install required system dependencies (in particular, Multipass; requires sudo and currently works only on Debian-based systems):
69
+ ```bash
70
+ agsekit prepare
71
+ ```
72
+
73
+ 5. Create the virtual machines defined in YAML:
74
+ ```bash
75
+ agsekit create-vms
76
+ ```
77
+
78
+ To launch just one VM, use `agsekit create-vm <name>`. If the config contains only one VM, you can omit `<name>` and it will be used automatically. If a VM already exists, the command compares the desired resources with the current ones and reports any differences. Changing resources of an existing VM is not supported yet.
79
+
80
+ 6. Mount your folders (assuming mounts are already configured in the YAML file):
81
+ ```bash
82
+ agsekit mount --all
83
+ ```
84
+
85
+ 7. Install all configured agents into their default VMs:
86
+ ```bash
87
+ agsekit install-agents --all-agents
88
+ ```
89
+
90
+ 8. Launch an agent inside its VM (example runs `qwen` in the folder where `/host/path/project` is mounted, with backups enabled by default):
91
+ ```bash
92
+ agsekit run qwen /host/path/project --vm agent-ubuntu
93
+ ```
94
+ On the very first run with backups enabled, the CLI creates an initial snapshot with progress output before launching the agent, so wait for it to complete.
95
+
96
+ ## agsekit commands
97
+
98
+ ### Setup and VM lifecycle
99
+
100
+ * `agsekit prepare` — installs required system dependencies (including Multipass; requires sudo and currently works only on Debian-based systems).
101
+ * `agsekit config-gen [--config <path>] [--overwrite]` — interactive wizard that asks about VMs, mounts, and agents, then writes a YAML config to the chosen path (defaults to `~/.config/agsekit/config.yaml`). Without `--overwrite`, the command warns if the file already exists.
102
+ * `agsekit config-example [<path>]` — copies `config-example.yaml` to the target path (defaults to `~/.config/agsekit/config.yaml`). If the default config already exists, the command skips copying.
103
+ * `agsekit create-vms` — creates every VM defined in the YAML configuration.
104
+ * `agsekit create-vm <name>` — launches just one VM. If the config contains only one VM, you can omit `<name>` and it will be used automatically. If a VM already exists, the command compares the desired resources with the current ones and reports any differences. Changing resources of an existing VM is not supported yet.
105
+ * `agsekit shell [<vm_name>] [--config <path>]` — opens an interactive `multipass shell` session inside the chosen VM, applying any configured port forwarding. If only
106
+ one VM is defined in the config, the CLI connects there even without `vm_name`. When multiple VMs exist and the command runs in
107
+ a TTY, the CLI prompts you to pick one; in non-interactive mode, an explicit `vm_name` is required.
108
+ * `agsekit ssh <vm_name> [--config <path>] [<ssh_args...>]` — connects to the VM over SSH using `~/.config/agsekit/ssh/id_rsa` and forwards any extra arguments directly to the `ssh` command (for example, `-L`, `-R`, `-N`).
109
+ * `agsekit portforward [--config <path>]` — starts a dedicated `agsekit ssh` tunnel for each VM that defines `port-forwarding` rules, monitoring the child processes and restarting them if they exit. Stop with Ctrl+C to gracefully terminate the tunnels.
110
+ * `agsekit start-vm <vm_name> [--config <path>]` — starts the specified VM from the configuration. If only one VM is configured, the name can be omitted.
111
+ * `agsekit start-vm --all-vms [--config <path>]` — starts every VM declared in the config file.
112
+ * `agsekit stop-vm <vm_name> [--config <path>]` — stops the specified VM from the configuration. If only one VM is configured, the name can be omitted.
113
+ * `agsekit stop-vm --all-vms [--config <path>]` — stops every VM declared in the config file.
114
+ * `agsekit destroy-vm <vm_name> [--config <path>] [-y]` — deletes the specified VM from Multipass. Without `-y`, the CLI asks for interactive confirmation.
115
+ * `agsekit destroy-vm --all [--config <path>] [-y]` — deletes every VM from the configuration, with the same confirmation requirement.
116
+ * `agsekit systemd install [--config <path>]` — writes `~/.config/agsekit/systemd.env` with absolute paths to `agsekit`, the config, and the current project directory, then registers and starts the user unit from `systemd/agsekit-portforward.service` via `systemctl --user` (link, daemon-reload, start, enable).
117
+ * `agsekit systemd uninstall` — stops and disables the user unit, then removes the linked `systemd/agsekit-portforward.service` from systemd via `systemctl --user`.
118
+
119
+ ### Mount management
120
+
121
+ * `agsekit mount --source-dir <path> [--config <path>]` — mounts the directory described by `source` in the configuration file (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) into its VM using `multipass mount`. Use `--all` to mount every entry from the config. When there is only one mount in the config, the command can be run without `--source-dir` or `--all`.
122
+ * `agsekit umount --source-dir <path> [--config <path>]` — unmounts the directory described by `source` in the config (or `CONFIG_PATH`/`--config`); `--all` unmounts every configured path. If only one mount is configured, the command will unmount it even without explicit flags.
123
+
124
+ ### Backups
125
+
126
+ #### One-off backup
127
+
128
+ `agsekit backup-once --source-dir <path> --dest-dir <path> [--exclude <pattern> ...] [--progress]` — runs a single backup of the source directory into the specified destination using `rsync`.
129
+ The command creates a timestamped directory with a `-partial` suffix, supports incremental copies via `--link-dest` to the previous backup, and honors exclusions from `.backupignore` and `--exclude` arguments. When finished, the temporary folder is renamed to a final timestamp without the suffix. If nothing changed relative to the last backup, no new snapshot is created and the tool reports the absence of updates.
130
+ Pass `--progress` to forward rsync progress flags and show a console progress bar while files are copied.
131
+
132
+ `.backupignore` examples:
133
+ ```
134
+ # exclude virtual environments and dependencies
135
+ venv/
136
+ node_modules/
137
+
138
+ # ignore temporary and log files by pattern
139
+ *.log
140
+ *.tmp
141
+
142
+ # include a specific file inside an excluded folder
143
+ !logs/important.log
144
+
145
+ # skip documentation build artifacts
146
+ docs/build/
147
+ ```
148
+
149
+ Backups use `rsync` with incremental links (`--link-dest`) to the previous copy: if only a small set of files changed, the new snapshot stores just the updated data, while unchanged files are hardlinked to the prior snapshot. This keeps a chain of dated directories while consuming minimal space when changes are rare.
150
+
151
+ #### Repeated backups
152
+
153
+ * `agsekit backup-repeated --source-dir <path> --dest-dir <path> [--exclude <pattern> ...] [--interval <minutes>] [--skip-first]` — runs an immediate backup and then repeats it every `interval` minutes (defaults to five minutes). With `--skip-first`, the loop waits for the first interval before performing the initial run. After each backup it prints `Done, waiting N minutes` with the actual interval value.
154
+ * `agsekit backup-repeated-mount --mount <path> [--config <path>]` — looks up the mount by its `source` path in the configuration file (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) and launches repeated backups using the paths and interval from the config. When only one mount is present, `--mount` can be omitted; with multiple mounts, an explicit choice is required.
155
+ * `agsekit backup-repeated-all [--config <path>]` — reads all mounts from the config (default search: `--config`, `CONFIG_PATH`, `~/.config/agsekit/config.yaml`) and starts concurrent repeated backups for each entry within a single process. Use Ctrl+C to stop the loops.
156
+
157
+ ### Agent installation
158
+
159
+ * `agsekit install-agents <agent_name> [<vm>|--all-vms] [--config <path>] [--proxychains <value>]` — runs the prepared installation script for the chosen agent type inside the specified VM (or the agent's default VM if none is provided). If the config defines only one agent, you can skip `<agent_name>` and it will be picked automatically. Use `--proxychains <scheme://host:port>` to override the VM proxy for this installation or `--proxychains ""` to ignore it once.
160
+ * `agsekit install-agents --all-agents [--all-vms] [--config <path>] [--proxychains <value>]` — installs every configured agent either into their default VM or into every VM when `--all-vms` is set.
161
+
162
+ The installation scripts live in `agsekit_cli/agent_scripts/`: `codex` installs the npm CLI, `codex-glibc` builds the Rust sources with the glibc target and installs the binary as `codex-glibc`, and `qwen`/`claude-code` follow their upstream steps (the `qwen` script installs the qwen-code CLI). Other agent types are not supported yet.
163
+
164
+ ### Running agents
165
+
166
+ * `agsekit run <agent_name> [<source_dir>|--vm <vm_name>] [--config <path>] [--proxychains <value>] [--disable-backups] [--skip-default-args] [--debug] -- <agent_args...>` — starts an interactive agent command inside Multipass. Environment variables from the config are passed to the process. If a `source_dir` from the mounts list is provided, the agent starts inside the mounted target path in the matching VM; otherwise it launches in the home directory of the default VM. Unless `--disable-backups` is set, background repeated backups for the selected mount are started for the duration of the run. When no backups exist yet, the CLI first creates an initial snapshot with progress output before launching the agent and then starts the repeated loop with the initial run skipped. Arguments from `agents.<name>.default-args` are added unless `--skip-default-args` is set; if the user already passed an option with the same name (for example `--openai-api-key`), the default value is skipped. With `--debug`, the CLI prints every external command before executing it to help troubleshoot agent launches. Use `--proxychains <scheme://host:port>` to override the VM setting for one run; pass an empty string to disable it temporarily.
167
+
168
+ ### Interactive mode
169
+
170
+ In a TTY you don’t have to type full commands every time: the CLI can guide you through an interactive menu that fills in parameters for you.
171
+
172
+ * Run `agsekit` without arguments to open the interactive menu, choose a command, and select options such as the config path, mounts, or agent parameters.
173
+ * Start a command without mandatory arguments (for example, `agsekit run`) to automatically fall back to the interactive flow after the CLI prints a “not enough parameters” hint. Use `--non-interactive` if you prefer the usual help output instead of prompts.
174
+
175
+ ## Localization
176
+
177
+ The CLI reads the system locale and falls back to English if it cannot detect a supported language. You can override this behavior with the `AGSEKIT_LANG` environment variable:
178
+
179
+ ```bash
180
+ AGSEKIT_LANG=ru agsekit --help
181
+ ```
182
+
183
+ ## YAML configuration
184
+
185
+ The configuration file (looked up via `--config`, `CONFIG_PATH`, or `~/.config/agsekit/config.yaml`) describes VM parameters, mounted directories, and any `cloud-init` settings. A base example lives in `config-example.yaml`:
186
+
187
+ ```yaml
188
+ vms: # VM parameters for Multipass (you can define multiple)
189
+ agent-ubuntu: # VM name
190
+ cpu: 2 # number of vCPUs
191
+ ram: 4G # RAM size (supports 2G, 4096M, etc.)
192
+ disk: 20G # disk size
193
+ proxychains: "" # optional proxy URL (scheme://host:port); agsekit writes a temporary proxychains.conf and wraps Multipass commands automatically
194
+ cloud-init: {} # place your standard cloud-init config here if needed
195
+ port-forwarding: # Port forwarding config
196
+ - type: remote # Open port inside VM and pass connections to Host machine's port
197
+ host-addr: 127.0.0.1:80
198
+ vm-addr: 127.0.0.1:8080
199
+ - type: local # Open port on Host machine, and pass connections to VM's port
200
+ host-addr: 0.0.0.0:15432
201
+ vm-addr: 127.0.0.1:5432
202
+ - type: socks5 # Open socks5-proxy port inside VM, directing traffic to Host machine's network
203
+ vm-addr: 127.0.0.1:8088
204
+ mounts:
205
+ - source: /host/path/project # path to the source folder on the host
206
+ target: /home/ubuntu/project # mount point inside the VM; defaults to /home/ubuntu/<source_basename>
207
+ backup: /host/backups/project # backup directory; defaults to backups-<source_basename> next to source
208
+ interval: 5 # backup interval in minutes; defaults to 5 if omitted
209
+ vm: agent-ubuntu # VM name; defaults to the first VM in the configuration
210
+ agents:
211
+ qwen: # agent name; add as many as you need
212
+ type: qwen # agent type: qwen (installs and uses the `qwen` binary), codex, codex-glibc (installs the `codex-glibc` binary), or claude-code (other types are not supported yet)
213
+ env: # arbitrary environment variables passed to the agent process
214
+ OPENAI_API_KEY: "my_local_key"
215
+ OPENAI_BASE_URL: "https://127.0.0.1:11556/v1"
216
+ OPENAI_MODEL: "Qwen/Qwen3-Coder-30B-A3B-Instruct-FP8"
217
+ default-args: # arguments passed to the agent unless the user overrides them
218
+ - "--openai-api-key=my_local_key"
219
+ - "--openai-base-url=https://127.0.0.1:11556/v1"
220
+ vm: qwen-ubuntu # default VM for this agent; falls back to the mount VM or the first VM in the list
221
+ codex:
222
+ type: codex
223
+ claude:
224
+ type: claude-code
225
+ codex2:
226
+ type: codex-glibc
227
+ ```
228
+
229
+ > **Note:** Prefer ASCII-only paths for both `source` and `target` mount points: AppArmor may refuse to mount directories whose paths contain non-ASCII characters.