v2.0.0 · Recently · what's new →

Your own AI agent, on your machine.

Headless browser. Sandboxed shell. Plug-and-play skills. Trusted Python add-ons. Free, open source, works with any OpenAI-compatible endpoint.

🎉 A new version is available.
$ pip install agent-code
Python 3.10+ License MIT + GPL-3.0 Platform Windows · Linux · macOS
⬇ Download View on PyPI

What's inside

A focused tool that does a few things well.

🌐

Headless browser

Real Chromium via DrissionPage. Renders JS, follows redirects, sees the actual page.

🛡️

Sandboxed shell

Allowlisted commands only. CWD locked to workspace/.

📝

Markdown rendering

Agent answers render with headers, bold, lists, links, code blocks.

🧠

Claude Code workflow

Plans with todos. Reads before editing. Prefers edit_file.

🕵️

Subagents

Isolated research tasks with a fresh context. Only the final answer returns.

🔒

Permission modes

readonly, ask, or auto. You decide.

🧩

Plug-and-play skills

Drop a folder in skills/. Restart. Tools appear.

🔧

Trusted add-ons

Python extensions that add UI, services, tools, and lifecycle hooks. See the Add-ons page.

🔑

Any OpenAI-compatible API

OpenAI, Together, Groq, DeepSeek, local vLLM. Keys rotate.

Quick start

Three commands. No build step.

1

Install the package

pip install agent-code
2

Run the agent

agent-code

On first launch, paste your API key in Settings.

3

Install bundled skills

agent-code skills install

Copies three skills into ./skills/.

Download

Latest release: , published .

⬇ Source (ZIP)

Snapshot of the latest release. Extract and run.

Download .zip
latest

⬇ Source (TAR.GZ)

Same content, gzipped tarball.

Download .tar.gz
~50 KB

🎯 Release notes

See what changed on GitHub.

Open release →
GitHub
RecommendedPyPI
pip install --upgrade agent-code

Platform notes

PlatformExtra setup
Windows 10/11pip install agent-code. If browser: FAILED, install WebView2 Runtime.
macOS 12+pip install agent-code — done.
Ubuntu / Debian / Kali / Mintsudo apt install python3-tk chromium
Fedora / RHEL / CentOSsudo dnf install python3-tkinter chromium
Arch / Manjarosudo pacman -S tk chromium
Headless / Cloud Shell / WSLCLI works. GUI needs a display.

Skills

A skill is a folder. That's the whole system.

Skills vs add-ons: skills are drop-in skill.py folders that add tools and chat commands. Add-ons are trusted Python extensions that can also add UI, replace services, and hook lifecycle events. Need more than tools? See the Add-ons page.

Bundled skills

📁

filesystem

Read, write, append, and list files inside the sandbox workspace.

🔍

public_api

Search 700+ free public APIs and call them directly.

✉️

temp_mail

Temp inbox via mail.tm / Guerrilla. Send via Resend, Brevo, or Mailjet.

Install, manage, publish

# Copy the three bundled skills into ./skills/
agent-code skills install

# List bundled skill names
agent-code skills list

# Open the Skill Store GUI (browse, install, disable, publish)
agent-code manage

Disabling a skill

Toggle it off in agent-code manage → Skills tab. Disabled skills are not imported on the next launch; their folder stays put so you can re-enable.

Add-ons

Trusted Python extensions stored in <user-data>/addons/<id>/. Each add-on ships addon.json and addon.py; helper modules may live beside them.

Add-ons run with your OS permissions. They are not sandboxed. Install only code you trust. A future signed repository with hashes, compatibility metadata, permission declarations, and review is planned.

Skills vs Add-ons

SkillAdd-on
What it isskill.py + skill.mdaddon.py + addon.json
Where it lives./skills/<name>/<user-data>/addons/<id>/
Adds agent toolsYesYes
Adds slash commandsYesYes
Adds UI widgetsNoYes — sidebar, toolbar, settings, workspace, addons
Replaces named servicesNoYes — explicit extension points only
Lifecycle hooksNoYes — e.g. tools_loaded
SandboxedTools run in workspace sandboxNo — full OS permissions

Directory layout

<user-data>/
└── addons/
    └── my-extension/
        ├── addon.json       ← metadata
        ├── addon.py         ← entry point (defines register(api))
        └── helpers.py       ← optional helpers

addon.json

{
  "name": "my-extension",
  "version": "1.0.0",
  "description": "Adds a custom workspace and alternate Skill Store client",
  "author": "Developer"
}

addon.py — skeleton

class AlternateSkillStore:
    base_url = "https://my-store.example/api"

    def list_skills(self):
        ...


def build_sidebar_button(parent, app):
    import customtkinter as ctk
    return ctk.CTkButton(parent, text="My workspace",
                         command=lambda: app.log_line("Workspace opened", "dim"))


def register(api):
    # Build arbitrary CustomTkinter widgets in supported application regions.
    api.register_ui("my-workspace", build_sidebar_button,
                    location="sidebar", label="My workspace")

    # Replace a named service when the relevant core integration opts in.
    api.register_service("skill_store", AlternateSkillStore(),
                         replaces=True, priority=50)

    # Add custom agent tools, slash commands, actions and lifecycle hooks too.
    api.on("tools_loaded", lambda **ctx: None)

UI extension points

register_ui(name, builder, location=..., label=..., description=...) supports sidebar, addons, settings, toolbar, and workspace. The builder receives (parent, app) and should return a Tk / CustomTkinter widget. The host places the returned widget. Keep builders fast — do not block the UI thread.

Service extensions

register_service(name, implementation, replaces=False, priority=0, description="") registers a named service implementation. AddonManager.resolve_service(name, default, app=...) returns the highest-priority registered implementation, or the supplied default. Factories can set addon_service_factory = True and accept app.

Service names are explicit extension points, not arbitrary monkey-patching of core globals. Core integrations must call resolve_service for a service before an add-on can replace it. Existing services remain the default when no add-on replacement is registered.

Lifecycle hooks

api.on("tools_loaded", callback) — subscribe to events. Callbacks receive keyword context.

Managing add-ons

Open the GUI with agent-code manage, then use the Add-ons tab. Disabling prevents the code from being imported on the next load — it does not reverse changes an already-loaded add-on made outside the API.

Trust and safety. Add-ons execute as your user account. Only install add-ons you trust. Do not run untrusted bundles from unknown sources.
ADDON-DEVELOPMENT.md ↗

Help

Everything you can type, click, and configure.

CLI commands

CommandWhat it does
agent-codeRun the agent GUI
agent-code runSame as agent-code
agent-code manageOpen the Skill Store GUI (skills + add-ons)
agent-code skills listShow bundled skill names
agent-code skills installCopy bundled skills into ./skills/
agent-code --versionPrint version
agent-code --helpShow all subcommands

Chat commands

CommandWhat it does
/resetClear conversation memory
/skillsList loaded + disabled skills
/todoShow the current task list
/saveSave chat locally + cloud
/permissionShow permission mode
/open <file>Open a workspace file
/emailConfigure email sending

File locations

PathWhat lives there
./skills/<name>/Skills you've installed or written
./workspace/Sandboxed filesystem the agent reads/writes
<user-data>/addons/<id>/Trusted add-ons (see Add-ons)
agent_config.jsonAPI keys and settings

Environment variables

VariablePurpose
AGENT_WORKSPACEAbsolute path to the workspace directory used by skills and tools

Troubleshooting

agent-code: command not found
export PATH="$HOME/.local/bin:$PATH"

Or use python3 -m agent_code.

browser: FAILED

Windows: install WebView2 Runtime.

Linux: sudo apt install chromium

_tkinter.TclError on Linux
sudo apt install python3-tk
no display name and no $DISPLAY

Headless machine — use CLI commands, or set up X11 forwarding.

LLM ERROR: 401

Wrong key, or stray quotes in agent_config.json.

LLM ERROR: 404

Model name doesn't match the provider.

BLOCKED: command not allowed

Sandbox rejected the command.

externally-managed-environment
pipx install agent-code
addon failed to load

Check that addon.py defines a top-level register(api) function, and that addon.json is valid JSON with at least name, version, description, author.

addon UI widget does not appear

Confirm the location= you passed to register_ui is one of sidebar, addons, settings, toolbar, workspace. Builders must return a widget and should not block.

add-on service replacement has no effect

The core integration must call AddonManager.resolve_service(name, default) for that service — add-ons cannot monkey-patch globals. Check that the service name matches.

1. Overview

A skill is a self-contained folder that adds tools and/or chat commands to the agent.

Why skills? You never edit the agent source. Drop a folder in skills/, restart.

2. Skill anatomy

your-project/
└── skills/
    └── my_skill/
        ├── skill.py       ← code: metadata + tools + commands
        └── skill.md       ← docs: frontmatter + human notes

3. The contract

Hello World

SKILL = {
    "name": "greet",
    "description": "Say hello.",
}

def hello(name: str = "world") -> str:
    return f"Hello, {name}!"

TOOL_SCHEMAS = [{
    "type": "function",
    "function": {
        "name": "hello",
        "description": "Return a greeting.",
        "parameters": {
            "type": "object",
            "properties": {"name": {"type": "string"}},
        },
    },
}]

TOOL_CALLABLES = {"hello": hello}

Chat commands

def _handle_ping(args: list, log) -> None:
    log(f"pong {' '.join(args)}", "dim")

COMMANDS = {
    "ping": {"handler": _handle_ping, "description": "Reply pong"},
}

Metadata — skill.md

---
name: my_skill
description: One-sentence summary.
version: 1.0.0
author: yourname
---

4. Sandbox

import os
from pathlib import Path
WORKSPACE = Path(os.environ["AGENT_WORKSPACE"]).resolve()

5. Publishing

  1. Sign in via agent-code manage
  2. Click Publish
  3. Others can install

6. Add-on development

Add-ons extend the agent beyond tools and chat commands — they can add UI widgets, replace named services, and subscribe to lifecycle events. Use them when a skill isn't enough. For the top-level overview and comparison table, see the Add-ons page.

Trust model: add-ons run with your OS permissions and are not sandboxed. Only ship code you'd run yourself.

Entry point — addon.py

Every add-on lives in <user-data>/addons/<id>/ and provides addon.py and addon.json. addon.py must define a top-level register(api) function; the host calls it exactly once on load.

{
  "name": "my-extension",
  "version": "1.0.0",
  "description": "Adds a custom workspace and alternate Skill Store client",
  "author": "Developer"
}
def register(api):
    api.register_ui("my-workspace", build_sidebar_button,
                    location="sidebar", label="My workspace")
    api.register_service("skill_store", AlternateSkillStore(),
                         replaces=True, priority=50)
    api.on("tools_loaded", lambda **ctx: None)

UI extension points

register_ui(name, builder, location=..., label=..., description=...) supports sidebar, addons, settings, toolbar, and workspace. The builder receives (parent, app) and must return a Tk / CustomTkinter widget. The host places the returned widget. Keep builders fast — do not block the UI thread.

def build_sidebar_button(parent, app):
    import customtkinter as ctk
    return ctk.CTkButton(parent, text="My workspace",
                         command=lambda: app.log_line("Workspace opened", "dim"))

Service extensions

register_service(name, implementation, replaces=False, priority=0, description="") registers a named service implementation. AddonManager.resolve_service(name, default, app=...) returns the highest-priority registered implementation, or the supplied default. Factories can set addon_service_factory = True and accept app.

Service names are explicit extension points, not arbitrary monkey-patching of core globals. Core integrations must call resolve_service before an add-on can replace a service. Existing services remain the default when no add-on replacement is registered.

Lifecycle hooks

api.on("tools_loaded", callback) — subscribe to events. Callbacks receive keyword context, and the return value is ignored.

Trust model

Disabling an add-on prevents its code from being imported on the next load; it does not reverse changes an already-loaded add-on made outside the API. A future signed add-on repository should support signatures, hashes, compatibility metadata, permission declarations, and review before installation.

Changelog

Loaded live from CHANGELOG.md · ·

Loading changelog from GitHub...

All releases on GitHub →