Skip to content
Open
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,21 @@ core.[0-9]*
secrets/**/*

# Registry build artifacts
toolchain/c/build/
toolchain/c/vendor/
toolchain/c/libs/
toolchain/c/sysroot/
software/*/bin/
registry/native/target/
registry/native/vendor/
registry/native/c/build/
registry/native/c/vendor/
registry/native/c/libs/
registry/native/c/.cache/
toolchain/c/.cache/
registry/native/c/sysroot/
registry/software/*/wasm/
software/*/wasm/
registry/software/*/agentos-package.meta.json
registry/.last-publish-hash
registry/software/*/.last-publish-hash
Expand Down
31 changes: 31 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ the guest — over inventing a softer fallback that hides the failure.
- Agent adapters must use real upstream SDKs. Do not replace SDK adapters with
direct API-call stubs.

## Software Build (WASM Toolchain)

Registry software is **real upstream Linux software** (GNU coreutils, grep, sed,
gawk, real curl/sqlite/duckdb/vim, …) compiled to `wasm32-wasip1` against a
**sysroot we fully own** — a patched Rust std + libc whose gaps are filled by
custom host-syscall imports. Treat that target as **native POSIX**;
`wasm32-wasip1` is an implementation detail, not a feature ceiling.

- **We do not depend on stock WASI / wasi-libc.** The sysroot is ours. A missing
libc/POSIX API (`getrlimit`/`RLIMIT_NOFILE`, `getgroups`, spawn, fd dup, …) is
never a blocker — implement it (real, or a sane stub) in the patched
std/libc/host-import layer. "WASI doesn't have X" is not a reason to stop; X is
ours to add.
- **Fix portability one layer down, in the sysroot** — a new std/libc patch or a
new host import — not with `cfg(target_*)` branches or shims in the tool's own
source. A WASM-specific branch in application code usually means the fix
belongs in the libc layer.
- **Patch the real upstream tool only as a fallback**, when the fix genuinely
cannot live in the sysroot. Patching the real tool is allowed; reimplementing
it is not.
- **"NOT POSSIBLE" is reserved for genuine impossibility** after exhausting both
sysroot patches and tool patches — never for a missing syscall we could
implement. Document the specific wall if you claim it.
- **Working in `software/`, you may (and should) fix the layer underneath.** When
a package behaves differently from real Linux, the root cause is usually not the
package — it's the runtime. It is in-scope and expected to fix the underlying
implementation: the Node-compat / bridge layer, the WASM execution runtime, the
kernel/VFS syscalls, or the patched sysroot/libc. Do **not** paper over a
Linux-deviating behavior in the package, its wrapper, or its test — chase it
down into whichever runtime layer owns it and make that layer match Linux.

## Publishing

- `scripts/publish` is the source of truth for npm/crates discovery, version
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,14 @@ Browse pre-built agents, tools, filesystems, and software packages at the [agent
| `@agentos-software/git` | git | git version control | rust | - | - |
| `@agentos-software/grep` | grep | GNU grep pattern matching (grep, egrep, fgrep) | rust | - | - |
| `@agentos-software/gzip` | gzip | GNU gzip compression (gzip, gunzip, zcat) | rust | - | - |
| `@agentos-software/http-get` | http-get | Minimal HTTP GET fetch helper | c | - | - |
| `@agentos-software/jq` | jq | jq JSON processor | rust | - | - |
| `@agentos-software/ripgrep` | ripgrep | ripgrep fast recursive search | rust | - | - |
| `@agentos-software/sed` | sed | GNU sed stream editor | rust | - | - |
| `@agentos-software/sqlite3` | sqlite3 | SQLite3 command-line interface | c | - | - |
| `@agentos-software/tar` | tar | GNU tar archiver | rust | - | - |
| `@agentos-software/tree` | tree | tree directory listing | rust | - | - |
| `@agentos-software/unzip` | unzip | unzip archive extraction | c | - | - |
| `@agentos-software/wget` | wget | GNU wget HTTP client | c | - | - |
| `@agentos-software/wget` | wget | GNU Wget file downloader | c | - | - |
| `@agentos-software/yq` | yq | yq YAML/JSON processor | rust | - | - |
| `@agentos-software/zip` | zip | zip archive creation | c | - | - |

Expand All @@ -175,7 +174,7 @@ Browse pre-built agents, tools, filesystems, and software packages at the [agent
|---------|-------------|----------|
| `@agentos-software/build-essential` | Build-essential WASM command set (standard + make + git + curl) | standard, make, git, curl |
| `@agentos-software/common` | Common WASM command set (coreutils + sed + grep + gawk + findutils + diffutils + tar + gzip) | coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip |
| `@agentos-software/everything` | All available WASM command packages in a single bundle | coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip, curl, zip, unzip, jq, ripgrep, fd, tree, file, yq, codex-cli |
| `@agentos-software/everything` | All available WASM command packages in a single bundle | coreutils, sed, grep, gawk, findutils, diffutils, tar, gzip, curl, wget, zip, unzip, jq, ripgrep, fd, tree, file, yq, codex-cli |
<!-- END PACKAGE TABLE -->

## License
Expand Down
4 changes: 2 additions & 2 deletions crates/client/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ fn wasm_command_mounts() -> Vec<MountConfig> {

/// Locate the materialized coreutils package under the in-repo registry build.
pub fn coreutils_package_dir() -> Option<PathBuf> {
let registry_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../software/coreutils/dist/package");
let registry_dir =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../software/coreutils/dist/package");
if registry_dir.join("agentos-package.json").is_file() {
return std::fs::canonicalize(registry_dir).ok();
}
Expand Down
3 changes: 1 addition & 2 deletions crates/client/tests/shell_pty_packages_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ fn coreutils_package_path() -> Option<PathBuf> {
}
}
for dir in [
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../software/coreutils/dist/package"),
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../software/coreutils/dist/package"),
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../node_modules/@agentos-software/coreutils/dist/package"),
] {
Expand Down
28 changes: 26 additions & 2 deletions crates/execution/assets/runners/wasi-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -1393,11 +1393,18 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
return this._measureWasiPhase("writeResultPtr", () => this._writeUint32(nwrittenPtr, written));
}
}
const entry = this._descriptorEntry(descriptor);
const localHostPassthrough =
handle.kind === "host-passthrough" &&
entry?.kind === "file" &&
entry.realFd === handle.targetFd;
const position = handle.append
? this._measureWasiPhase("appendFstat", () =>
Number(__agentOSFs().fstatSync(handle.targetFd).size ?? 0)
)
: null;
: localHostPassthrough
? (entry.offset ?? 0)
: null;
const written = this._measureWasiPhase("writeSync", () =>
__agentOSFs().writeSync(
handle.targetFd,
Expand All @@ -1407,6 +1414,15 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
position,
)
);
if (localHostPassthrough) {
if (handle.append) {
entry.offset = this._measureWasiPhase("appendFstat", () =>
Number(__agentOSFs().fstatSync(handle.targetFd).size ?? 0)
);
} else {
entry.offset = (entry.offset ?? 0) + written;
}
}
return this._measureWasiPhase("writeResultPtr", () => this._writeUint32(nwrittenPtr, written));
}
if (handle?.kind === "guest-file" && typeof handle.targetFd === "number") {
Expand Down Expand Up @@ -1745,15 +1761,23 @@ if (typeof globalThis !== "undefined" && typeof globalThis.__agentOSWasiModule =
(handle?.kind === "passthrough" || handle?.kind === "host-passthrough") &&
typeof handle.targetFd === "number"
) {
const localEntry = this._descriptorEntry(descriptor);
const localHostPassthrough =
handle.kind === "host-passthrough" &&
localEntry?.kind === "file" &&
localEntry.realFd === handle.targetFd;
const totalLength = this._boundedReadLength(iovs, iovsLen);
const buffer = Buffer.alloc(totalLength);
const bytesRead = __agentOSFs().readSync(
handle.targetFd,
buffer,
0,
totalLength,
null,
localHostPassthrough ? (localEntry.offset ?? 0) : null,
);
if (localHostPassthrough) {
localEntry.offset = (localEntry.offset ?? 0) + bytesRead;
}
const written = this._writeToIovs(iovs, iovsLen, buffer.subarray(0, bytesRead));
return this._writeUint32(nreadPtr, written);
}
Expand Down
Loading