Inspiration
AI coding assistants and LLM orchestrators are getting good at planning, but they still need a safe way to actually touch a developer's machine — clone a repo, check a branch, stage changes, run a command.
The usual options were unsatisfying: give the model raw shell access and you invite command injection and accidental destructive commands; use a full Git GUI or a thick AI agent and you get locked into one workflow, one platform, or one model.
We wanted the missing middle layer: a small, cross-platform, security-first execution agent that any frontend, desktop app, or LLM orchestrator could call — without handing it the keys to the whole machine. What it does
cross-platform-agent is a thin local execution layer for Windows, Linux, and macOS. It exposes local Git and command capabilities as structured, controlled APIs over REST and WebSocket.
It currently supports 13 Git actions plus a generic command action:
git.clone, git.add, git.checkout_branch, git.commit, git.merge_branch
git.fetch, git.pull, git.push, git.status
git.get_current_branch, git.diff_staged, git.list_branches, git.list_branches_structured
command.run
It provides:
REST API: GET /health, GET /actions, GET /tasks, POST /tasks, GET /tasks/{task_id} — running by default at http://127.0.0.1:8787
WebSocket API: ws://127.0.0.1:8787/ws, with execute, get_task, and ping client messages, and welcome, task.accepted, task.updated, task.snapshot, pong, and error server messages
Async tasks: every execution returns a task with queued → running → succeeded / failed status, plus logs, result, error, and timestamps
Security by default: listens only on 127.0.0.1, restricts paths to AGENT_ALLOWED_ROOTS (default: user HOME), never runs commands through a shell, and by default only allows git in command.run
Human-friendly failures: when git.commit, git.pull, or git.push fails, it also tries to show a native desktop dialog, so a local user sees the problem even if they aren't watching the API response
It deliberately does not do natural-language understanding, multi-step planning, long-term memory, or model orchestration. It is the execution gateway — the upper layer decides what to do, this decides how to do it safely. How we built it
We built it in Rust (Edition 2024) as a single lightweight service, using:
Axum 0.8 + Tokio for the async REST and WebSocket server
Serde / Serde JSON for structured action parameters and results
UUID for task IDs, tower-http for CORS, tracing for logging
The system git client for all Git operations — the agent validates parameters, invokes Git, and returns the result, rather than re-implementing Git
The code is split by responsibility:
src/actions/git.rs / command.rs — the individual actions
src/path_policy.rs — path whitelist validation
src/process_runner.rs — shell-free process execution with timeouts
src/config.rs — environment-based configuration
src/runtime.rs / server.rs — task lifecycle and REST/WebSocket handling
src/dialog.rs — native error dialogs per platform
Configuration is entirely via environment variables, e.g. AGENT_HOST, AGENT_PORT, AGENT_API_TOKEN, AGENT_ALLOWED_ROOTS, AGENT_ALLOWED_PROGRAMS, GIT_BINARY, AGENT_COMMAND_TIMEOUT_MS (default 300,000 ms), and AGENT_RETAINED_TASKS (default 200).
Authentication, when AGENT_API_TOKEN is set, works via Authorization: Bearer , X-API-Token, or a WebSocket ?token= query parameter.
To run it: bash
cargo run
Challenges we ran into
No shell, no shortcuts: Every command had to be expressed as a structured program + args + cwd triple. That blocks pipelines, redirection, and curl ... | sh style attacks, but it also meant carefully designing every Git action's parameters instead of passing a string through.
Cross-platform really means cross-platform: Native error dialogs differ everywhere — osascript on macOS, PowerShell MessageBox on Windows, and zenity / kdialog / notify-send on Linux. Path separators differ too (: on Unix, ; on Windows). The happy path was easy; the platform edge cases were the work.
Making Git failures useful: A failed git push or git pull isn't just an exit code — it can be an auth failure, a protected branch, or a merge conflict. We had to preserve Git's original error in the API result while still surfacing a clear native dialog to the local user.
Keeping REST and WebSocket consistent: The same task lifecycle had to be queryable over REST (GET /tasks/{id}) and pushable over WebSocket (task.updated / task.snapshot) without duplicating logic.
Resisting feature creep: The hardest challenge was saying no. Every tempting feature — planning, memory, prompt handling — would have made the agent thicker, harder to audit, and harder for multiple callers to share. Keeping it thin was a deliberate, ongoing decision.
Accomplishments that we're proud of
A working, genuinely cross-platform agent in Rust with 14 structured actions behind one clean REST + WebSocket interface.
Security as the default, not an option: localhost-only binding, path whitelists, program whitelists, no shell execution, and optional token auth from day one.
End-to-end Git workflow tests: automated tests cover clone, add, commit, checkout_branch, merge_branch, push, pull, and command.run, using locally created bare and collaborator repositories to verify the full workflow.
Clear boundaries: the README explicitly states what the project is not — not a Git GUI, not an AI orchestrator — which makes it easy for a web frontend, desktop app, LLM orchestrator, or future MCP adapter to build on top of it.
A minimal, dependency-light footprint: just cargo run with Rust 1.96+ and Git 2.x.
What we learned
A thin layer is a feature. Separating execution from understanding, planning, and memory keeps the tool layer auditable, reusable, and easy to reason about — and lets the upper layer evolve independently.
Structured input beats string input. Once commands are data (program, args, cwd) instead of shell text, whole classes of injection and parsing bugs disappear.
Security has to be the default configuration. If the safe path requires extra setup, most users won't take it. Localhost-only, HOME-only, and git-only defaults make the safe choice the easy choice.
Error UX matters as much as API design. An LLM caller needs structured JSON; a human at the desk needs a native dialog. Both have to carry the same original error.
Cross-platform support lives in the details — dialogs, path separators, and external tool behavior — not in the headline claim.
What's next for cross-platform-agent
Staying true to the thin execution-layer定位, the next steps are:
Add an MCP adapter, so the agent can be exposed directly to the model-tool ecosystem
Add filesystem actions, alongside the existing Git and command actions
Add IDE / browser / desktop actions
Add approval policies, so sensitive actions can require explicit human confirmation
Add persistent audit logging, beyond the current in-memory retained tasks
What we will not add to this repository: natural-language understanding, planners, or long-term memory. Those belong in the upper layer that calls this agent.
Project: coldwateryi/cross-platform-agent — v0.1.0, MIT License
Log in or sign up for Devpost to join the conversation.