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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions content/blog/2026-04-21-skills-vs-bundled-tools/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
title: "Skills Over Bundled Tools: Why Composable AI Beats Platform Primitives"
date: 2026-04-21
slug: skills-vs-bundled-tools-ai-agent-platforms
description: "Hermes v0.10.0 bundles 4 platform tools. Molecule AI installs them as skills. This piece explains why composability beats convenience for production multi-agent teams."
tags: [skills, hermes, comparison, composability, AI-agents, tutorial]
---

# Skills Over Bundled Tools: Why Composable AI Beats Platform Primitives

Hermes v0.10.0 launched Tool Gateway — a set of built-in tools (web search, image generation, TTS, browser automation) available to paid Portal subscribers. If you're evaluating AI agent platforms, you might see a feature list comparison that looks like this:

- **Hermes:** Has web search, image gen, TTS, browser automation — *out of the box*
- **Molecule AI:** Doesn't seem to have these — *requires skill installation*

That reading is fair. It's also incomplete.

This piece explains what "skills" actually means on Molecule AI, why composability is structurally different from bundled tools, and how the two approaches serve fundamentally different use cases.

## What "Bundled Tools" Actually Means

Hermes Tool Gateway ships four capabilities as platform-level primitives. When you use them, you're using the same implementation every other Hermes user uses — same models, same rate limits, same behavior. You can't swap the image generator for a different one. You can't add a new tool to the bundle without a platform update.

**The appeal is real:** sign up, start using, no configuration. That's excellent for a personal productivity tool.

**The limitation is structural:** you get what's shipped. Your agent's capabilities are defined by what the platform vendor decided to include, and they don't change until the vendor ships an update.

## What "Skills" Actually Means

Skills on Molecule AI are installable capability packages — analogous to npm packages or pip modules, but for agent capabilities. A skill packages:

- **Tool definitions** — the JSON Schema interfaces the agent sees
- **Runtime code** — the actual implementation (API calls, local processing, etc.)
- **Configuration** — sensible defaults, required env vars, scoping rules
- **Versioning** — install a specific version, upgrade when ready

The browser automation skill on Molecule AI isn't a different feature from Hermes' browser tool — it uses the same underlying technology (Chrome DevTools Protocol over WebSocket). The difference is *how you install it* and *what you can do with it*:

```bash
# Install browser automation skill
molecule skills install browser-automation

# Install TTS skill (alternative: use your own provider)
molecule skills install tts --provider openai

# Install a community skill
molecule skills install arxiv-research --from community
```

After installation, your agent sees the tools the same way it sees any other tool — they're first-class in the agent's tool context. But unlike Hermes' bundled approach, you can:

- Swap the TTS provider (OpenAI → ElevenLabs → self-hosted)
- Version-pin to a known-good skill release
- Inspect the skill code (it's just Python)
- Contribute a new or improved version back to the community
- Run the same skill locally or on any cloud VM

## The Composability Difference

Bundled tools are a feature set. Skills are a *package manager*. The difference matters as your agent stack grows.

**With bundled tools:** you use what ships. If the image generator doesn't support a format you need, you file a feature request.

**With skills:** you combine what's available. Need a specialized tool that doesn't exist? Write a skill and install it. Someone else already built it? Install it with one command.

The skills ecosystem on Molecule AI already covers the same ground as Hermes Tool Gateway — browser automation, TTS, image generation, web search — plus dozens of additional capabilities contributed by the community. You start from zero by default, which has higher first-run friction. But the ceiling is open.

## The Production Trust Angle

For **individual developers**, bundled tools win on first-impression convenience. For **production teams**, the calculus is different.

When you deploy an agent in production, you need to answer:

- *Who has access to which tools?* (auth)
- *Which pipeline used which tool?* (audit)
- *Can I revoke access without a redeploy?* (operations)

Hermes Tool Gateway is designed for personal Portal accounts — there's no org-level auth, no per-user scoping, no audit trail across a team.

Molecule AI's skills run inside workspaces, which means they inherit the full access control model:

- **Org-scoped API keys** — name, revoke, and audit every integration independently
- **Workspace-level secrets** — per-agent credential management, no shared tokens
- **Audit trail** — `org:keyId` on every request, chain of custody from minting to use

If you're evaluating a platform for a team, the question isn't just "does it have tools?" — it's "can I trust those tools in a production context?" Molecule AI's answer to that question is the skills ecosystem *plus* the auth layer that surrounds it.

## The Unified Narrative

Here's how the comparison lands:

> **Hermes** is a personal AI assistant — batteries included, no auth model, no team features.
>
> **Molecule AI** is a platform your team can build on, ship to customers, and trust in production — with skills for every capability Hermes bundles, org-level auth, and audit on every call.

If you want to evaluate Molecule AI's skills coverage, start here:

→ [MCP browser automation guide](/blog/browser-automation-ai-agents-mcp) — browser tools via Chrome DevTools Protocol, same capability as Hermes' built-in browser
→ [TTS and image generation skills](/docs/guides/skill-catalog) — community-contributed, versioned, swappable
→ [Org-scoped API keys](/docs/guides/org-api-keys.md) — production auth and audit

**The tagline:** Batteries included is nice. Batteries you can trust, extend, and audit is a platform.

---
*Skills are how Molecule AI delivers the same capabilities as Hermes Tool Gateway — with more flexibility, more control, and a production trust model built in. Browse the full skill catalog on GitHub or install directly via the CLI.*
4 changes: 2 additions & 2 deletions content/docs/agent-runtime/workspace-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ External workspaces run outside the platform's Docker infrastructure — on your
| Liveness | Docker health sweep | Heartbeat TTL (90s offline threshold) |
| Registration | Automatic at container start | Manual: `POST /workspaces` + `POST /registry/register` |
| Token | Inherited from container env | Minted at registration, shown once |
| Secrets | Baked in image or env var | Pulled from platform at boot via `GET /workspaces/:id/secrets/values` |
| Secrets | Baked in image or env var | Pulled from platform at boot via `GET /workspaces/:id/secrets` |

### Registration flow

Expand Down Expand Up @@ -190,7 +190,7 @@ The platform returns a 256-bit bearer token — save it, it is shown only once.
**3. Pull secrets at boot:**

```bash
curl http://localhost:8080/workspaces/ws-xyz/secrets/values \
curl http://localhost:8080/workspaces/ws-xyz/secrets \
-H "Authorization: Bearer <your-token>"
```

Expand Down
191 changes: 191 additions & 0 deletions content/docs/api/workspace-files.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
title: Workspace File Copy API
description: API reference for the workspace file copy and write operations, including CWE-22 path traversal protection.
---

# Workspace File Copy API

> **Source:** `workspace-server/internal/handlers/container_files.go` + `templates.go`
> **Handler:** `TemplatesHandler.WriteFile` → `copyFilesToContainer`
> **Security:** CWE-22 path traversal protection (PRs #1267, #1270, #1271)

`copyFilesToContainer` is the internal Go implementation that powers workspace file write operations. It is not called directly by API clients — clients reach it through the HTTP handler `PUT /workspaces/:id/files/*path`.

## Endpoint Overview

`PUT /workspaces/:id/files/*path` writes a single file to a workspace container or its config volume.

```
PUT /workspaces/:id/files/*path
Authorization: Bearer <workspace-token>
Content-Type: application/json

{
"content": "string"
}
```

The handler (`TemplatesHandler.WriteFile`) validates the path, then routes to one of two backends:

| Workspace state | Backend | Method |
|---|---|---|
| Container running | Docker `CopyToContainer` (tar) | `copyFilesToContainer` |
| Container offline | Ephemeral Alpine container | `writeViaEphemeral` → `copyFilesToContainer` |

Both paths use `copyFilesToContainer` internally. The ephemeral container path mounts the config volume as `/configs` and calls the same function, so CWE-22 protection applies regardless of container state.

## Function Signature

```go
func (h *TemplatesHandler) copyFilesToContainer(
ctx context.Context,
containerName string,
destPath string,
files map[string]string, // filename → content
) error
```

| Parameter | Type | Description |
|---|---|---|
| `ctx` | `context.Context` | Request-scoped context |
| `containerName` | `string` | Docker container name or ID |
| `destPath` | `string` | Target directory inside the container (typically `/configs`) |
| `files` | `map[string]string` | Map of relative filenames to file contents |

## Parameters

### `containerName`

The running container for the workspace. Resolved by `TemplatesHandler.findContainer`, which checks three candidates in order:

1. Platform provisioner naming convention (`ws-<uuid>`)
2. The full workspace container ID
3. The workspace name from the database (spaces replaced with dashes)

If the container is not running, `findContainer` returns `""` and the handler falls back to `writeViaEphemeral`.

### `destPath`

The directory inside the container where files are written. In normal operation this is `/configs`, which is mounted from the platform-managed config volume. All file operations are constrained to this volume.

### `files` (`map[string]string`)

A map of relative filenames to their string content. File names are **relative paths only** — absolute paths and `..` traversal sequences are rejected before the tar header is written.

## Security Notes

### CWE-22 Path Traversal Protection

**PRs #1267, #1270, #1271** added path traversal protection at the tar-archive-write boundary.

Before these PRs, `copyFilesToContainer` used raw map keys as tar header names without validation:

```go
// Before — UNSAFE
header := &tar.Header{
Name: name, // name came directly from map key
Mode: 0644,
Size: int64(len(data)),
}
```

A malicious caller embedding `../` in a file name could write outside the volume mount. Now:

```go
// After — SAFE (PRs #1267 / #1270)
clean := filepath.Clean(name)
if filepath.IsAbs(clean) || strings.HasPrefix(clean, "..") {
return fmt.Errorf("unsafe file path in archive: %s", name)
}
archiveName := filepath.Join(destPath, name)
header := &tar.Header{
Name: archiveName, // always inside destPath
Mode: 0644,
Size: int64(len(data)),
}
```

The validation works in three stages:

1. **`filepath.Clean`** normalizes the path (removes redundant separators, resolves `.`).
2. **Absolute path check** (`filepath.IsAbs`) rejects any path that resolves to an absolute OS path.
3. **`..` prefix check** (`strings.HasPrefix`) rejects paths that would escape the destination via parent-directory traversal.

The resulting `archiveName` is always inside `destPath`, so the tar header can never write outside the mounted volume regardless of input.

> **Defense in depth:** `WriteFile` (the HTTP handler) also calls `validateRelPath(filePath)` **before** passing the path to `copyFilesToContainer`. This closes the gap for any future caller that bypasses the handler-level check. Do not remove handler-level `validateRelPath` when modifying this code.

### Handler-Level Validation (`validateRelPath`)

```go
func validateRelPath(relPath string) error {
clean := filepath.Clean(relPath)
if filepath.IsAbs(clean) || strings.HasPrefix(clean, "..") {
return fmt.Errorf("path traversal blocked: %s", relPath)
}
return nil
}
```

`validateRelPath` is called at the start of every file operation handler (`WriteFile`, `ReadFile`, `DeleteFile`, `ListFiles`). Invalid paths return `400 Bad Request` with `{"error": "invalid path"}`.

Allowed root paths are also allow-listed: `root` must be one of `/configs`, `/workspace`, `/home`, or `/plugins`. Other values return `400 Bad Request`.

## Error Codes

`copyFilesToContainer` returns errors directly. The `WriteFile` HTTP handler wraps them:

| HTTP status | Condition | Response body |
|---|---|---|
| `400 Bad Request` | `validateRelPath` rejects the path (traversal attempt) | `{"error": "invalid path"}` |
| `400 Bad Request` | Malformed JSON body | `{"error": "invalid request body"}` |
| `404 Not Found` | Workspace not found in database | `{"error": "workspace not found"}` |
| `500 Internal Server Error` | Docker unavailable | `{"error": "failed to write file: docker not available"}` |
| `500 Internal Server Error` | Tar header write failure | `{"error": "failed to write file: failed to write tar header for <name>: ..."}` |
| `500 Internal Server Error` | Docker `CopyToContainer` failure | `{"error": "failed to write file: <docker error>"}` |

## Example

### Write a file to a workspace

```bash
curl -X PUT https://platform.example.com/workspaces/ws-abc123/files/claude.md \
-H "Authorization: Bearer <workspace-token>" \
-H "Content-Type: application/json" \
-d '{
"content": "# My Agent\n\nThis agent specializes in code review.\n"
}'
```

**Success response (`200 OK`):**

```json
{
"status": "saved",
"path": "claude.md"
}
```

### Path traversal rejected

```bash
curl -X PUT https://platform.example.com/workspaces/ws-abc123/files/../../etc/passwd \
-H "Authorization: Bearer <workspace-token>" \
-H "Content-Type: application/json" \
-d '{"content": "hacked"}'
```

**Rejection response (`400 Bad Request`):**

```json
{
"error": "invalid path"
}
```

## Related

- [Platform API Reference](./platform-api.md) — full API endpoint table
- [Workspace Runtime](../agent-runtime/workspace-runtime.md) — runtime environment model
- `workspace-server/internal/handlers/templates.go` — `WriteFile`, `validateRelPath`
- `workspace-server/internal/handlers/container_files.go` — `copyFilesToContainer`, `writeViaEphemeral`
6 changes: 5 additions & 1 deletion content/docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ Step-by-step guides for common Molecule AI integrations and configurations.
## Authentication

- [Org-Scoped API Keys](/docs/guides/org-api-keys) — Mint, audit, and revoke named API keys for organization-level access. Replace `ADMIN_TOKEN` with scoped credentials for scripts, agents, and CI.
- [Workspace Auth Tokens](/docs/architecture/workspace-auth-tokens) — Technical deep-dive on per-workspace bearer token authentication (Phase 30.1).
- [Workspace Auth Tokens](/docs/architecture/workspace-auth-tokens) — Technical deep-dive on per-workspace bearer token authentication (Phase 30.1).

## Skills

- [Skill Catalog](/docs/guides/skill-catalog) — Install, manage, and publish skills for browser automation, TTS, image generation, web search, and more. Covers CLI commands, version pinning, and the skill lifecycle.
4 changes: 2 additions & 2 deletions content/docs/guides/remote-workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Laptop (remote agent) Molecule AI Platform
│ POST /registry/register ────────────► │ ← admin token (one-time)
│ ←─ auth_token (256-bit) ◄────────── │ ← shown once, saved to disk
│ │
│ GET /workspaces/:id/secrets/values │ ← bearer: auth_token
│ GET /workspaces/:id/secrets │ ← bearer: auth_token
│ POST /registry/heartbeat (30s loop) │
│ GET /workspaces/:id/state (30s loop)│
│ │
Expand Down Expand Up @@ -135,7 +135,7 @@ The agent appears on the canvas with a **purple REMOTE badge** within seconds. F
| Phase | What shipped | Endpoint |
|---|---|---|
| 30.1 | Workspace auth tokens | `POST /registry/register`, `POST /registry/heartbeat` |
| 30.2 | Token-gated secrets pull | `GET /workspaces/:id/secrets/values` |
| 30.2 | Token-gated secrets pull | `GET /workspaces/:id/secrets` |
| 30.3 | Plugin tarball download (remote install) | `GET /plugins/:name/download` |
| 30.4 | Workspace state polling (no WebSocket needed) | `GET /workspaces/:id/state` |
| 30.5 | A2A proxy enforces caller token | `POST /workspaces/:id/a2a` |
Expand Down
Loading