Skip to main content

AIStack

tests

A self-hosted home for your AI coding skills. Every skill on your computers is backed up to a private space of your own and kept current on each machine. From there you share skills with a team, or with a group inside one, and take the ones your teammates shared.

You see and manage all of it on a web dashboard. Your coding agent is connected to the same account over MCP, so the everyday actions are also a sentence in chat.

Open source and free, with no hosted service: it runs on your own computers, and you choose where your Library lives.

Where your Library lives

Choice What it costs you For
Only this computer Nothing to set up. No other computer reaches it Trying it out, or one computer
A private GitHub repository Sign in to GitHub once in the browser on each computer; AIStack creates the repository and keeps it up to date. Nothing to keep switched on, nothing pauses, and every change is in the repository's history (ADR-0025) Your laptop and your desktop
A free PostgreSQL database (Neon recommended; Supabase works) A free account and one address pasted on each computer. Nothing to keep switched on. Supabase pauses after about a week unused Your laptop and your desktop
A computer that stays on, running the AIStack server Free on a computer you own, but it must stay on (or a few dollars a month on a host) Teams, and the only choice that works for one

For the first three, your coding agent starts AIStack itself and nothing runs in between (ADR-0020). Install uv, then paste this into Claude Code or Codex:

Set up AIStack: run `uvx aistack-mcp setup` and follow what it prints.

It asks which choice you want, signs you in to GitHub and creates the private repository, or walks you through creating the Neon database, connects your agent, and installs the sync-aistack skill. On another computer, paste the same and choose the same repository or database. If a laptop is lost, revoke GitHub CLI under Settings → Applications on github.com and sign in again on the others with aistack-mcp github, or reset the database password and paste the new address on the others (ADR-0023). "Open my aistack dashboard" opens your Library in the browser, on that computer only, as it does on a server.

For a team, run a server and connect each computer to it: see Connecting a computer to a server.

To move your own Library onto a team's server later, run aistack-mcp export on a computer that has it. It writes every skill in the Library, at its latest revision, to ./aistack-library, with each skill's files checked against the hash recorded when they were pushed. Then connect that computer to the server, copy the skill folders you want into ~/.claude/skills, and say "sync aistack": sync backs up whatever the new Library lacks, without asking. Revision history and which computer had which skill stay behind.

Try it in two minutes

No coding agent and no Docker needed to look around. This starts a server on your machine and fills it with a small demo team:

git clone https://github.com/dchristopoulos/AIStack && cd AIStack
poetry install
AISTACK_INVITE_CODE=try-it-out-locally-1234 poetry run aistack serve &
poetry run python scripts/demo_seed.py http://127.0.0.1:8000 try-it-out-locally-1234

It prints a sign-in link for each of three people: an admin, a member with an update waiting, and a member who is kept out of a private group. Open one. Everything the script created went through the same tools and routes a real user's agent and browser use.

What it looks like

The Library: every skill you keep, where it came from, and which computers have it

One computer's coding agent A team, with nested groups
What Claude Code on one computer has, with Add skills and Copy from A team's skills by group
Managing groups An update a teammate pushed, offered and not applied
Groups: open or private, and who is in them An update waiting in the Library, in dark mode

The problem

A team writes a good code-review skill. It gets copied onto five laptops, and within a month there are five versions. A developer with a MacBook and a desktop maintains the same setup twice. Nobody knows what their teammates' agents are running, and nobody wants a colleague's edit to an agent's instructions landing on their machine unannounced.

How it is organized

Three places hold skills. Each is more widely seen than the one before.

Place Who sees it How a skill gets there What installs from it
Library Only you Sync backs up what is on your computers, without asking Your machines, and nothing else
A team Its members A member shares one of their own skills Nothing directly: you take a copy into the Library
A group in a team, such as backend or backend/aistack Open: whoever sees the group above it. Private: the people added to it A member of it, or of a group above it, shares into it The same as a team

The Library is yours alone and mirrors your computers. It is the only place a machine ever installs from, so what runs on your laptop is always your own copy.

Teams are created by any user, who becomes the team's admin, and joined with the team's invite code. A team's invite link also creates the account of someone new to the server. Sharing gives the team its own copy of a skill, with its own history, and every revision says who shared it.

Groups sit inside a team like channels, and nest. A private group is seen only by the people added to it. Two roles exist, admin and member; "backend developer" is not a role, it is being in the backend group.

Two rules follow from "a machine installs only from the Library":

  • A teammate's change is an offer. When a team's copy is newer than the one you took, sync tells you who changed it and asks. It never applies the change. A skill is instructions to an agent, and somebody else's edit does not reach your machine until you say yes.
  • Leaving a team takes nothing off your computer. Neither does being removed from one, or an admin deleting the team's copy. What you took is yours.

The reasoning is in ADR-0015.

How it works

flowchart LR
    subgraph people["A person"]
        browser["Browser"]
        agent["Coding agent<br/>on each machine"]
    end
    subgraph server["AIStack server · one container, one port"]
        dash["Dashboard<br/>/app and /api"]
        tools["13 MCP tools<br/>metadata only"]
        routes["2 transfer routes<br/>signed, 5-minute URLs"]
        mine[("Library<br/>private, per user")]
        teams[("Teams and groups<br/>shared copies")]
        desired[("Desired state<br/>per machine")]
    end

    browser -- "sign-in link from chat" --> dash
    agent -- "MCP · machine token" --> tools
    agent -- "tar + curl" --> routes
    dash --- mine
    dash --- teams
    tools --- mine
    tools --- desired
    routes --- mine
    mine -- "share" --> teams
    teams -- "take" --> mine

The dashboard is the product; MCP is the connector. Both are the same process and call the same services. The dashboard is one HTML page with no build step, and there are no passwords: you ask your agent to open it and get a link that signs your browser in (ADR-0017).

Skills are copied to disk in full and run natively. After an install there is no runtime dependency on AIStack: if the server is down, every skill still works (ADR-0001).

The server holds desired state per machine. Each machine has its own token, so the server knows who you are and which computer you are on. A MacBook and a work laptop can enable different skills, and sync reconciles each against its own list (ADR-0004).

Metadata travels over MCP; bytes do not. A tool argument is text the model generates token by token, which is slow and not exact for a file. So push_skill and get_skill return a short-lived signed URL, and the agent moves a gzipped tar with curl (ADR-0010).

Sync never loses your work

Sync compares one machine with the Library. It acts without asking only when nothing can be lost and nothing is shared.

What sync finds What happens
Enabled here, not on disk Installed
Enabled here, an older unmodified revision Updated, and you are told who pushed the new one
On disk, not yet in the Library Backed up to the Library. It is private, so nothing is shared
Enabled here, edited locally You are asked: restore, back up your version, or leave it
You removed it earlier, and the folder is still there You are asked: bring it back, or delete the folder
In the Library and on disk, never chosen on this machine You are asked whether to manage it here
A team has a newer revision of a skill you took Offered, with who pushed it. You decide each time

Sync itself only reports, and the agent acts through other tools; the one thing it records is when each folder last synced. It never deletes a file, never changes what a machine has enabled, and never applies a teammate's change. Every answer that would overwrite your files or discard edits says so first. The policy is one table in reconciliation.py, with tests that state it as invariants over every row (ADR-0006).

Connecting a computer to a server

Paste this into Claude Code or Codex, with your server's address and an invite code: the one its operator gave you, or a team's, which also puts you in that team:

Set up AIStack: fetch https://aistack.example.com/setup and follow it.
Invite code: <the invite code>. Call me <your name>.

The agent registers this computer, installs the sync-aistack skill, and connects itself to the server. Restart it once when it says so. That restart is the only one: the agent reads its server list at startup, and skills installed afterwards load live.

One connected agent is enough for the whole computer. Sync keeps Claude Code's skills folder and the shared ~/.agents/skills folder in line, and that shared folder is what Codex, Gemini CLI, Kimi Code, Cursor, Copilot CLI, opencode, and Amp read. The first time, it asks once whether both folders should hold the same skills (ADR-0021). Setup also offers to sync at the start of every session: a SessionStart hook that only reminds the agent to run sync-aistack, so your computers stay current without anyone asking.

For a second computer, say "add my other computer to aistack" on one that is already connected. It returns one command to paste there. No invite code is involved.

Things to say in chat

Say What happens
"sync aistack" Backs up what is new on this computer, installs and updates what it should have, and asks about the rest. Do it when you start work, like git pull, or let the session-start hook ask for it
"open my aistack dashboard" Gives you a link that opens the dashboard, signed in
"what skills do my teams have?" Lists your own skills, then each team's, with who pushed each
"install code-review from backend" Takes the team's copy into the Library and installs it here. Usable in the same session
"share wizard with backend" Gives the team a copy of your latest revision
"create a team called backend" Creates it with you as admin. "Invite someone to backend" returns its invite code
"join a team with this code" Joins the team a teammate's invite code belongs to
"remove wizard from this machine" Disables it here and deletes the local folder. It stays in the Library and on your other machines

A revision records who pushed it. That is provenance, not authorship: people back up and share third-party skills, and AIStack never claims you wrote something you only uploaded.

Everything else is on the dashboard: which of your computers has which skill, groups and who is in them, members and roles, moving or removing a team's skill, unsubscribing from a group's updates, leaving a team, and removing a skill from the Library.

Running a server

docker run -d --name aistack -p 8000:8000 \
  -e AISTACK_INVITE_CODE="$(openssl rand -base64 24)" \
  -v aistack-data:/data ghcr.io/dchristopoulos/aistack:latest

The image is published from every commit on main that passed the tests, for Intel and ARM machines; docker build -t aistack . builds the same thing from a checkout. One container, one volume, SQLite inside. The dashboard needs no second service and no extra port. Point DATABASE_URL at PostgreSQL and the same image uses that instead (ADR-0002). An upgrade is starting the new image: the server migrates its database at startup, in one transaction (ADR-0016).

To look around without setting anything up, fill a fresh server with a small demo team. It prints a dashboard link for each of three people, and everything it creates goes through the same tools and routes a real user's agent and browser use:

poetry run python scripts/demo_seed.py http://127.0.0.1:8000 "<the invite code>"

The screenshots in this README are retaken the same way, from a fresh server, by scripts/retake_screenshots.py.

Operating AIStack covers configuration, reaching the server from other machines, backups, upgrades, and the operator's tasks: revoke a machine, recover a locked-out user, take someone off the server, rotate the invite code.

The thirteen tools

MCP carries what is worth saying in a sentence. Anything that would need many typed parameters is a dashboard action instead.

Tool What it does
Connect join Redeem the server's or a team's invite code, register your first machine, get its token
add_machine Register another computer and return what to paste there, for Claude Code or Codex
Library sync Compare one skills folder on this machine with the Library and report what to do. Changes no skill and no decision
push_skill Get a signed URL to back a skill up to. A repeated name becomes a new revision
get_skill Enable a skill on this machine and get a signed URL for its files. With a team, take that team's copy first
list_skills Your own skills, then every team skill you may see. This is also the search
disable_skill Stop managing a skill on this machine
Teams share_skill Copy your latest revision into a team or a group
list_teams The teams you are in, your role, and their size
create_team Create a team with you as its admin
invite_to_team Make an invite link for a team. It lets in one person, creating their account if they are new, and lasts seven days. Cancel one in the dashboard
join_team Join a team with its invite code
Dashboard open_dashboard Return a link that signs a browser in for you

Reading the code

About 5,800 lines of Python and 6,900 of tests. A good order:

  1. docs/DESIGN.md for the plan and CONTEXT.md for the vocabulary the code uses.
  2. mcp/tools/: the thirteen tools. Each is thin: read the caller, open one transaction, call a service.
  3. mcp/auth/: two principals, one rule, enforced in one middleware so a new tool is covered by default.
  4. services/visibility.py: who may see what, in one module. Group access is plain code over two queries, and something you may not see answers exactly like something that does not exist.
  5. services/skill_service.py for push, share, and take, and services/team_service.py for team administration, where every function begins by establishing the caller's place in the team.
  6. services/reconciliation.py: the sync policy as a pure table, and services/sync_service.py, which computes its inputs and the team updates on offer.
  7. The push pipeline, which is the trust boundary: transfer_ticket.py → routes/transfer.py → skill_archive.py → skill_validation.py.
  8. db/schema.py and db/migrations/versions/: the schema as migrations. 0002_teams.py is the one that moved a shared vault into teams without hiding anything from anyone.
  9. api/dashboard.py: the dashboard's sign-in and JSON routes, as thin as the tools, and services/dashboard_service.py, which assembles what one user may see.

Things in there that took some care:

  • Untrusted archives. An uploaded tar is decompressed under a hard ceiling before tarfile sees it, read in memory, and never extracted. Links, devices, and sparse files reject the push. Tests build a gzip bomb and a pax-header bomb and assert both are refused.
  • Paths every filesystem can hold. A skill pushed from Linux must install on Windows and macOS, so reserved device names, trailing dots, case-only differences, and NFC/NFD twins are rejected at push.
  • YAML without surprises. Frontmatter is parsed with every scalar kept as a string, so version: 1.10 is not stored as 1.1, nothing can construct an object, and an alias bomb is stopped by a node budget.
  • Hidden means indistinguishable. A team you are not in, a private group, and a skill inside one are answered with the same exception and the same sentence as one that does not exist. A test holds a table of what every tool shows an outsider, and fails when a tool is added without a row.
  • Migrations that roll back whole. Every pending migration runs in one transaction on both databases. On SQLite that took turning foreign keys off around the table rebuild and checking them before commit; a test rebuilds a referenced table with rows in it and fails without that.
  • Races proven, not hoped. Two pushes reading the same latest revision are held at a threading.Barrier in the exact window, so the test fails without the unique constraint. It runs against real PostgreSQL in CI.
  • One hash, one implementation. The server computes the content hash at push from the bytes and at sync from the digests the agent sent. Its test asserts a constant derived with perl, shasum, and xxd.
  • Secrets stay out of logs. Machine tokens, both kinds of invite code, transfer URLs, and dashboard links never reach a log line or an error. Tests assert it per flow, and CI greps the container's own output.
  • A dashboard with no passwords. The sign-in link and the session cookie are signed tickets with different purposes, so neither can stand in for the other, and a request that changes anything must carry a header a cross-site form cannot send.
  • Two databases, one behavior. Every service and HTTP test runs against SQLite and PostgreSQL. Concurrent writers wait instead of failing on SQLite, which took an explicit BEGIN IMMEDIATE (ADR-0014).

Tests

poetry install
poetry run pytest

The PostgreSQL half skips without a database. To run all of it:

docker compose -f compose.test.yaml up -d
TEST_DATABASE_URL=postgresql+psycopg://aistack:aistack@localhost:5433/aistack_test poetry run pytest

The GitHub half skips too: a Library in a private GitHub repository is otherwise tested against a local stand-in. To run it against real GitHub, with your gh login and a private repository kept for this alone (every run adds to its history):

AISTACK_E2E_GITHUB_REPO=<owner>/aistack-library-e2e poetry run pytest tests/e2e

CI also builds the container and drives it from outside as two users: one pushes and shares, the other takes the skill and gets the same bytes, across a restart and on both databases.

How the whole thing was verified

What the repository itself shows:

  • The test suite covers each service directly and each flow over real HTTP with real bearer tokens: join, push, install, sync, teams and invite codes, share and take, team updates, and the dashboard from sign-in to an update taken there.
  • The upgrade from a single shared vault is tested on a database built at the old schema: afterwards everyone sees what they saw before, and a failed migration leaves the database as it was.
  • The dashboard page is driven in a real browser against the real server: sign-in from a link, taking an update, sharing into a nested group, creating and joining a team, what a member is not shown, and a phone-width layout. The same tests run in Chromium, Firefox, and WebKit.
  • The container job runs the built image, not the source tree.

What it does not show, stated plainly:

  • The first release, before teams, was also run end to end by fresh agents that were given only what a real client gets: the tool descriptions and the /setup page. That exercise has not been repeated for sharing, taking, team updates, or the dashboard. Those rest on the tests above.

  • A signed-in Claude Code session was run once by hand against a local server (2026-10-14): it listed a team's skills over MCP, installed one with the tool's own curl and tar steps, executable bit intact, and used it in the same session without a restart. The first attempt to use the skill straight after installing it answered "Unknown skill"; a moment later it was found. No automated test repeats this.

  • The session-start hook was checked in a headless Claude Code session (2026-10-14): its line reached the model's context at startup.

  • A whole sync was run once from a signed-in Claude Code session (2026-10-14, Claude Code 2.1.241, headless, against a local server). The computer's skills folder held 25 real skills and the server was arranged so one sync met every part of the report. Told only "sync aistack", and given its two answers in advance, the session hashed the folder, called sync, and then:

    The report said What the session did
    24 skills already current Nothing
    1 to install, pushed from another computer and switched on from the dashboard Installed it, executable bit intact
    1 to update, an untouched older revision Replaced it with revision 2
    2 on disk and not yet in the Library Backed both up without asking
    1 edited locally Left it alone, as answered, and said the edits exist only on that computer
    1 update offered by a team, pushed by a teammate Took it, as answered, and named who pushed it

    A second sync straight afterwards reported nothing to do except the edited skill, which it asks about again. It took 14 turns, about three minutes, and $0.95. One thing went wrong on the way: the session wrote its install commands with shell variables in an rm, Claude Code refused that as unsafe, and the session rewrote them with plain paths and carried on. The folders the run added were removed afterwards and the skills folder was checked to be file for file what it had been. This is one run with one model, and no automated test repeats it.

  • A sync was run once from Codex (2026-10-15, Codex CLI 0.162 from the ChatGPT app, codex exec, against a local server). It was connected the way setup connects it, with an [mcp_servers.aistack] block in ~/.codex/config.toml that codex mcp get aistack listed. Its ~/.agents/skills held sync-aistack and one skill the Library did not have, and the Library held one skill switched on for that folder but missing from it. Told only "sync aistack", Codex loaded sync-aistack, synced only the agents folder because there was no ~/.claude, hashed it, called sync, backed up the new skill without asking, and installed the missing one byte for byte. Three things got in the way, all from Codex's own safeguards and not from AIStack:

    • Non-interactive codex exec refuses an MCP tool that needs approval, so the run set default_tools_approval_mode = "approve" for the aistack server on the command line. Setup now writes approval_mode = "approve" for sync, get_skill, and push_skill only, so sync runs without a prompt and every other AIStack tool still asks. Codex 0.162 was checked to read those lines; a sync was not run again with them.
    • Codex's workspace-write sandbox keeps .agents read-only, so the first install failed and Codex said so. The upload still went through. The install succeeded once the run was given write access, which stands in for the user approving the write. Setup leaves that approval in place: declaring ~/.agents/skills a writable root would let every Codex session, in any repository, change the skills every agent on the computer loads.
    • The codex 0.146 on the PATH rejected the model configured in that file, so the run used the CLI the ChatGPT app ships.

    The skills folder was a scratch home directory, and the config file was restored from a backup afterwards. This is one run with one model, and no automated test repeats it.

  • Local mode was run once by hand (2026-10-15, Claude Code 2.1.241, headless). Two scratch home folders stood in for a laptop and a desktop, each starting the built package with uvx --from <wheel> aistack-mcp over stdio, and both pointed at one SQLite Library. Told "sync aistack", the laptop's session backed up its one skill. Asked what was in the Library and to install it, the desktop's session listed it, synced, and installed it byte for byte with its executable bit, in six turns. No server ran at any point. This is one run with one model; tests/mcp/test_local_mode.py repeats the same flow without an agent.

  • Local setup was run once by hand (2026-10-15) in a scratch home folder, against the built package and a PostgreSQL address pasted as postgresql://. Each step of aistack setup worked as written: the start check stayed silent on success and printed one sentence for a wrong password. Claude Code 2.1.241, headless and connected with setup's own command, was then told "sync aistack". It loaded the local sync-aistack skill, synced both folders, backed up the one skill on disk, and asked the first-sync question about using the same skills in both folders. Setup's claude mcp add itself was not run, because it would have changed the real Claude Code configuration.

  • A sync was run from Codex in local mode (2026-10-15, Codex CLI 0.162 from the ChatGPT app, codex exec). It was connected with setup's Codex settings given on the command line, with a scratch home folder and the built package. Told "sync aistack", it loaded the local sync-aistack skill and backed up the one skill in ~/.agents/skills. With that folder deleted, a second sync installed it again, byte for byte. AIStack wrote the folder itself, so Codex's sandbox had nothing to approve. The first attempt found no server: AIStack's first start downloaded its packages for longer than Codex waits, which is why setup runs AIStack once before connecting the agent, and why its Codex settings now allow a minute.

  • Every chat request a person makes was run against a server from source (2026-10-15), once in headless Claude Code 2.1.241 and once in codex exec (Codex CLI 0.162 from the ChatGPT app), each in a scratch home folder, connected as a seeded user with a machine token and nothing else. The phrases were "sync aistack" (with a new skill, an edited one, and an older revision on disk), "what skills do my teams have?", "install release-notes from aistack", "share pr-description with aistack", "remove … from this machine", "create a team called platform and invite someone", and "add my other computer". Both agents did each one. What got in the way, and what changed:

    • Claude Code wrote its install steps as a shell function with rm -rf "$STAGE/$name", which it refuses to run, in every session that installed something. It recovered each time, at the cost of a few turns. get_skill now says to run the steps per skill with the paths written out, and the same sync then took 14 turns instead of 20.
    • Asked to add a computer where the user runs Codex, Codex got only a claude mcp add command, searched the web, and improvised a setup that needs an environment variable set forever. add_machine now also returns the config.toml block setup writes, which Codex then showed as it was.
    • Codex left out the team when told "install release-notes from aistack", was refused, and listed every skill to find it. The refusal now names the teams the caller can see that share a skill of that name, so the next call succeeds.
    • Both agents compared a team's revision number with the user's own, which count separately. list_skills now says so.

    The sessions ran with their permission checks off, inside a macOS sandbox that denied writes to the real skills folders and agent configuration. This is one run per agent with one model each, and no automated test repeats it.

  • A Library was shared through a real private GitHub repository (2026-10-15, Claude Code 2.1.241 headless, the built package through uvx, two scratch home folders as a laptop and a desktop, the owner's gh login). aistack check on each reported the repository. Told "sync aistack", the laptop's session backed up its skill, which put four event files in the repository. Asked what was in the Library and to install it, the desktop's session listed it and installed it byte for byte with its executable bit, in seven turns. The first attempt found two faults, fixed before this run: a clone that timed out left a broken clone that later passed for a working one, so changes were marked shared without being pushed; and git asked the macOS keychain helper Homebrew configures before gh, which hung. AIStack now clones into a temporary folder and moves it into place, and gives git gh's login alone for GitHub.

  • The finished local setup was run on a new computer against that same repository (2026-10-15): AIStack installed with uv tool install, started by its full path with a PATH holding only /usr/bin:/bin, as an agent launched from a desktop app may have. Headless Claude Code, told "sync aistack", synced both folders and offered the two skills the Library had from the other computers, in seven turns. codex exec, told "sync aistack" and to accept the offer, installed both into ~/.agents/skills. Its first attempt found no AIStack tools, because every start then pulled from GitHub before answering and Codex does not wait for a slow MCP server; a start now needs no network after the first one.

  • Local mode was walked end to end again (2026-10-15), from the built package installed with uv tool install into scratch home folders, on two computers for each place a Library can live: this computer only, a new private GitHub repository, and a PostgreSQL database pasted as postgresql://. Each computer was connected to headless Claude Code 2.1.241 or to codex exec (Codex CLI 0.162 from the ChatGPT app, with a Codex home holding only the login), and told what a person says: "sync aistack", "what's in my library?", "remove … from this computer", "install …", "open my aistack dashboard" (the page then used in a browser), and a skill edited on one computer and synced on the other. Each place worked on both computers. What got in the way, and what changed:

    • Two computers edited one skill before syncing. The second was offered to push its copy with nothing to say the first had pushed since, and pushing it made the first computer's edit disappear from both. Sync now reports the revision a changed copy was edited from, and the second computer's session then said that pushing would replace revision 4 from the other computer.
    • Codex asked before list_skills, which the first sync on a computer calls to offer the Library, so codex exec could not make the offer. Setup now approves it, and the same run then offered the three skills the other computers had.
    • A push made with GitHub out of reach was reported as backed up. push_skill now says when a change has not reached GitHub, and Codex told the user their other computers get it once a sync reaches GitHub.
    • aistack check on a new, empty repository warned that it could not reach it, then said it was ready.
    • Asked to move a Library from the computer to GitHub, Claude Code improvised claude mcp add without --scope user. The sync skill now describes the move. Asked again, the session laid out export, setup, removing the old connection, and copying back, and those steps, run by hand, moved the computer's skills into the GitHub Library.
    • The dashboard beside the terminal kept showing a skill Codex had just removed. It now refreshes when its window comes back to the front.
    • Two Windows problems were found by reading setup and fixed without a Windows computer: PowerShell 5.1's > writes the sync skill as UTF-16, and a Windows path in the Codex block breaks TOML. aistack sync-skill <folder> now writes the file itself.

    Left as it is: a skill holding a binary file or more than 100 files is refused with a sentence saying why, and is listed under backup again on every sync. Setup's own claude mcp add and Codex config file were not used; each agent got the same settings on its command line. These are single runs with one model per agent, and the fixes each have a test.

How this compares

Checked in October 2026.

AIStack Claude Code's claude.ai sync Plugin marketplaces A git repo of skills
Share a skill A sentence in chat, or a click Upload on claude.ai A commit A commit
Per-machine choice of skills Yes No No No
Handles local edits Asks, never overwrites Overwrites Replaces on update Merge conflicts
Needs Your own server An Anthropic account Git credentials on every machine Git everywhere
Works beyond Claude Code Planned next No No Manually

For one developer on one harness, Claude Code's own sync is the simpler choice, and AIStack does not try to beat it. AIStack is for people who want their own server, a private space per person with teams and groups on top, per-machine control, and one source of truth across more than one coding agent, which is why a second harness is the first thing on the roadmap.

Status and roadmap

Built: skills for Claude Code, Codex, and every agent that reads ~/.agents/skills, a private space per user, teams with invite codes, nested groups that are open or private, share and take, team updates as offers, schema migrations, and the dashboard.

Not built, in the order they are likely to arrive:

  1. Every agent on a computer on the dashboard: a page per skills folder, and agents in the Add to… dialog.
  2. Rules, the second resource type, projected into CLAUDE.md, AGENTS.md, and .cursor/rules/.
  3. Background sync. Today nothing happens until you say "sync aistack".
  4. More on the dashboard: server administration as buttons, version pinning, deleting a single revision.
  5. Hooks, agents, an MCP gateway, and profiles.

Deliberately left out (ADR-0015): automatic updates from a team, a permission matrix of roles and groups, and merging. When your copy and the team's have both changed, taking the team's replaces your latest, which stays in your history; nothing is merged line by line.

Known limits today:

  • Two skills folders: Claude Code's and the shared ~/.agents/skills. Project folders are not managed.
  • "Add my other computer" returns a Claude Code command. A second computer where Codex is the only agent is not connected that way yet.
  • No version pinning: a machine always gets the latest revision of your own copy.
  • One machine belongs to one server.
  • Restarting the server signs everyone out of the dashboard, because the signing key lives in memory. Machines stay connected.
  • Server administration (revoking a machine, recovering a locked-out user) is a documented script, not a button.
  • A skill is text files only, at most 100 of them, 256 KiB each and 1 MiB together, up to 8 folders deep. Empty folders are not stored and .env files are refused.

docs/DESIGN.md §4 lists everything left out and why.

Privacy

AIStack collects nothing. There is no telemetry, no analytics, no account with us, and no hosted service: it runs on computers you choose, and what it stores stays there.

What it connects to. A server makes no outbound connections. The dashboard loads nothing from any other site; its content security policy forbids it. In local mode, AIStack reaches only:

  • PyPI, once a day, to check for a newer release of itself;
  • your own GitHub repository, through your own gh sign-in, when your Library lives there;
  • your own database, when your Library lives in one.

What a server stores.

  • People: each person's username and, if they give one, the full name their teammates read.
  • Computers: each person's computers, by name and operating system, with a hash of each computer's token, never the token.
  • Skills: the skills people back up and share, with every revision and who pushed it.
  • Placements: which skill is switched on in which folder of which computer, and when each folder last synced.
  • Teams: teams, groups and who is in them, and invites, kept as a hash of their code with who made and used them.

The log records names and ids, and is built never to record an IP address, a token, an invite code, or a link. A local Library holds the same about one person. In a GitHub Library, its private repository holds it, including the skills' files.

Who is responsible. Whoever runs a server holds its users' data. A team that runs one for its people decides what to tell them. An operator can take someone off a server (docs/OPERATING.md), and that ends their access. Their username stays on the revisions they pushed, because a team needs to know whose instructions its agents run. Copies teammates took are theirs.

Security reports go through SECURITY.md.

Documentation

Decisions are written down before code, with the reasoning kept.

Stack

Python 3.13, FastMCP (and the Starlette application underneath it), SQLAlchemy, Alembic, PyJWT, Poetry. SQLite by default, PostgreSQL through DATABASE_URL. No front-end framework and no build step.

License

MIT. See LICENSE.

Metadata

Release files for aistack-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aistack-mcp 0.1.0
File Size Uploaded
aistack_mcp-0.1.0.tar.gz 190.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aistack-mcp 0.1.0
File Interpreter ABI Platform
aistack_mcp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 413.9 kB

Release files / aistack_mcp-0.1.0.tar.gz

Download URL aistack_mcp-0.1.0.tar.gz
Size 190.6 kB
Tags Source
SHA-256 checksum
How to use checksums
099b5b2535e739c78756ecf41709ea19ef50b6b36b5c3d46f0142d92c834e569
BLAKE2b-256 checksum
How to use checksums
d2f52f1bb36747d82590a8cf228b45d7ec961393498c84ac835861fc337e9d1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / aistack_mcp-0.1.0-py3-none-any.whl

Download URL aistack_mcp-0.1.0-py3-none-any.whl
Size 223.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
de0bd9847d2da6d0101de7d0371e3a9eecde5117b0f963b6b7f2740a0eed95b3
BLAKE2b-256 checksum
How to use checksums
cbd2dd6134f2478911a45649aba7585c5e2a2bc41e8fa80c8a7597872fbfe9c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page