agent-hub
AGENTS.md3 starsChanged 3 months agoMITView on GitHub
A local hub for Agent services: one configuration, one set of credentials, one aggregation point, shared by every AI client (Claude Code, Cursor, Codex, Open WebUI, and others). Go + Wails3, with a dual-mode gateway (stdio, one process per client / daemon, hosting the shared HTTP pool and the coordination plane). What a client may reach is decided in advance, by configuration, and never at call time. A server is on or off; a server offers all of its tools…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,774 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository6 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- new-featureSkill
- nightly-tidySkill
- releaseSkill
- security-auditSkill
- agenthubSkill
The file
We split this AGENTS.md into the rules it gives an agent
3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 160
Use one English, Conventional-Commits-shaped format:
- Distinctive: line 172
After one blank line, body uses the compact narrative style established by the repository's earlier commits: state the problem and why it …
- Distinctive: line 198
Run git config commit.template .gitmessage once per clone to show the same rules in the editor.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AgentHub
A local hub for Agent services: one configuration, one set of credentials, one aggregation point, shared by every AI client (Claude Code, Cursor, Codex, Open WebUI, and others). Go + Wails3, with a dual-mode gateway (stdio, one process per client / daemon, hosting the shared HTTP pool and the coordination plane).
What a client may reach is decided in advance, by configuration, and never at call time. A server is on or off; a server offers all of its tools or a named subset; a profile takes a subset of the servers and may narrow their tools further; a client follows a profile. Every layer intersects and none can widen. There is no approval queue, no runtime scope change, no scanning of what a downstream returned — an earlier design had all three and they were removed rather than left half-wired, because a governance surface that does not decide anything still reads as protection.
What remains outside that model is not permission: agent tokens grade the HTTP face, rate limits keep one runaway loop from burning a budget, and netguard / spawnguard refuse destinations and processes regardless of who asked.
What to read first
| File | When to read it |
| docs/architecture.md | How the system is carved up, and what a single call passes through |
| docs/flows.md | How a flow runs at runtime, and which way it falls on failure |
| docs/subsystems/ | Before touching a package — its invariants and failure directions |
| docs/status/oauth.md | An OAuth downstream will not connect, or which provider shapes are supported |
| docs/model.md | What a client is allowed to reach, and who decided it |
| docs/conventions.md | Whether a name, a dependency direction or a convention may change |
| docs/decisions/ | Why a settled question was settled that way |
| .agents/skills/ | You are about to do one of the standard workflows, and want its steps in order |
docs/ explains how the system works; skills are what you execute. Four repository workflows exist
today — new-feature (build anything, and land it),
nightly-tidy (the recurring simplify / refactor / docs-agree
pass), release (cut a release), and
security-audit (the recurring security sweep — a workflow
of parallel finders, adversarial verifiers and one adjudication pass). .agents/skills/ is the
single source; .claude/skills links to the entire directory so Codex and Claude execute the same
files.
Confirmed gaps, pinned to a line but not yet fixed, live in the docs/subsystems/ file of the package
that owns them — under "current assembly status", or beside the invariant they bend — and, for one
platform's overall state, in docs/status/windows.md. There is no separate backlog file:
a gap recorded next to the code it is about gets read by whoever touches that code, which is more
than a central list can claim.
Hard constraints (violations fail CI)
cmd/agenthub-guiandapimust not import anything underinternal/*— "the GUI is optional" is a compile-time constraintinternal/mcpdepends on the standard library only, and no otherinternal/*package may import a third-party MCP library — there is exactly one protocol facade, and it is in-houseinternal/pipelinemust not importinternal/ctlapi— the data plane does not depend on the control planeinternal/mcp,internal/platform,internal/logx, andinternal/guard/*are zero-business-dependency foundations
Each has a failing case in internal/depguardtest proving the rule actually blocks. A lint rule
configured but not in effect is worse than no rule.
The easiest things to get wrong when changing code
- The gate chain order is frozen: scope → token tier. Both decide from configuration alone, and both fail closed. Nothing in the chain may inspect or rewrite what a call carries.
- There is exactly one execution path: direct calls and
call_toolboth go throughpipeline.Execute. Any new path must assert its gate count matches a direct call. RouteOfis the only legitimate provenance for an exposed name; splitting on__is forbidden — a server id or tool name may itself contain__.- Security predicates must document their failure direction (fail-open or fail-closed); netguard's bidirectional predicates are the model.
- A tool selector is an allow list, never a deny list. The two answer the arrival of a tool the downstream adds tomorrow in opposite directions, and one file must not give two answers.
niland[]are different everywhere a selector appears: nil means "no rule",[]means "nothing".omitzero, notomitempty— dropping an empty list turns block-all into allow-all.- Isolation a config claims must be delivered or refused: for fields like
runtime: docker, fail closed rather than silently degrade to host execution. - Run
make fmtafter touching an import block..golangci.ymlenablesgofmt/goimportsunderformatters:, notlinters:, so onlymake lintcatches it —go buildandgo vetstay silent, and the error names theimport (line rather than the offending import. Order is alphabetical within a group ("cmp"before"context","maps"before"os").
Testing and verification
make # the target list, one line each
make fmt # apply gofmt + goimports (they are CI failures, see above)
make docs-gen # regenerate the generated blocks in docs/ (make ci checks them)
make ci # build + test + lint + generated docs
make ci-full # everything the CI workflow runs, plus the CI-shaped e2e run
make ci-landing # ci-full with the caches dropped (after the rebase, before landing)
make gui # build the GUI separately (excluded from build/lint by default)make ci is not CI. Three ways a local green run still goes red — all covered by make ci-full,
and all covered by the branch's own PR checks, which is why the skill spends the local run on the
landing rather than on every push:
- A skipped depguard proof is not a proof (docs/conventions.md#engineering-conventions). Without golangci-lint,
internal/depguardtestcallst.Skipandmake testcounts it as success; CI greps for--- SKIPand fails.make ci-depguard-proofis the local equivalent, and it greps a log it piped throughtee;ci-landingdoes the same over the whole ofci-full. Those two are the repository's onlyteed recipes, and each armsset -o pipefailitself because GNU Make 3.81, the/usr/bin/makeon macOS, ignores.SHELLFLAGSsilently.ci-fullneeds neither: it is a list of prerequisites with no recipe body of its own. - The
guijob, deliberately left out ofmake ciso "the GUI is optional" does not become a prerequisite of the default build. make guiis not that check. It runsnpm install, which repairs apackage-lock.jsonthat disagrees withpackage.json; CI runsnpm ci, which rejects. Onlygui-frontend-cireproduces it.
- e2e runs both ways, and
make ci-fullnow runs both for you:make testreaches the suite with this machine's environment, ande2e-ci(a prerequisite ofci-full) reaches it withXDG_RUNTIME_DIRset, as CI's Linux runner has it. That variable must not move the run directory — onlyAGENTHUB_DATA_DIRdoes, along with everything else — and this suite is the regression test for it.make e2eandmake e2e-cirun one shape alone;e2eclearsXDG_RUNTIME_DIRrather than inheriting it, so on a Linux box — wheremake testalready produces the CI shape — it is the one that covers the other. - Preconditions inside tests (the process was killed, the file exists) must fail hard, never
silently
return: a silent skip disguises an environment difference as some other component failing. - For hangs, add evidence before changing code: an e2e timeout SIGQUITs the process under test and folds the goroutine stacks into the failure message.
- Windows has only the cross-compilation gates
make cross-windows(build + vet, minus the Unix-only e2e and installer suites) andmake cross-windows-gui, with no real-machine verification. When touching a parser that reads untrusted input, run a round of fuzzing:
make fuzz FUZZ=FuzzParseMessage # one target; FUZZTIME=60s by default make fuzz # all eight, back to backEight targets, one per path by which external bytes arrive:
FuzzParseMessage(downstream JSON-RPC frames),FuzzSSEScanner(remote SSE streams, a hand-written line scanner — the least trustworthy),FuzzScanAuthParam(remoteWWW-Authenticate, hand-written index scanning),FuzzEncodeJSON(downstream tool results),FuzzScanTOMLServers(another application's config file, hand-written),FuzzBlankJSONCandFuzzSpliceEntryKeepsEverythingElse(the JSONC comment-blanking pass, and the splice that edits a settings.json in place without re-encoding it), andFuzzDecodeHeaderValue(theMcp-Namebase64 sentinel a caller's header carries, decoded before validation compares it to the body). They do not all live ininternal/mcp;FUZZ_TARGETScarries each one's package, so the target name alone runs it.make ciruns only the seed corpora — keep-fuzzout of CI.Adding a ninth means editing three places — the target, its
FUZZ_TARGETSentry, and the list above — andtest/buildrulesfails until all three agree. The omission is otherwise invisible:make cistill runs the seed corpus, so it looks covered whilemake fuzznever reaches it.
Reference implementations: read, never copy
mcpproxy-go (Go) and toolport (Rust) are reference material only; do not copy code. What is inherited is the list of problems they hit — which edge cases exist, what the failures look like, and what the correct behavior is.
Collaboration conventions
These are the rules, and only the rules. The commands live in the new-feature skill, in one copy — an order of operations reconstructed from the reasons below is how a step ends up in the wrong place.
Commit messages
Use one English, Conventional-Commits-shaped format:
<type>[(<scope>)][!]: <summary>- Type is one of
feat,fix,refactor,perf,test,docs,build,ci,chore,release, orrevert. - Scope is optional and names a stable owner such as
cli,gateway,gui, orbuildrules;guiis a scope, not a type. - Summary is an imperative sentence fragment, normally lowercase, with no terminal punctuation; keep the complete title within 80 characters.
- After one blank line, body uses the compact narrative style established by the repository's
earlier commits: state the problem and why it matters, describe the concrete change, then note
verification or an important limitation when useful. Prefer 2–4 short paragraphs and roughly
80–150 words total; omit details obvious from the diff, and omit body only when the title already
says everything. Put attribution and issue trailers last. Mark a breaking change with
!and aBREAKING CHANGE:trailer.
Examples:
docs(windows): document both Windows verification gates
The manual runbook named only cross-windows, so following it skipped the
GUI path where Wails diverges most from the macOS build.
On a fresh checkout, that GUI target also needs the gitignored frontend
bundle. Running it alone therefore fails before compiling any Windows code,
while ci-full hides the prerequisite by building the frontend first.
The runbook now includes cross-windows-gui and names the frontend build
prerequisite. Both targets were verified from a clean worktree.
docs: define the contributor commit convention
release: 0.18.0Run git config commit.template .gitmessage once per clone to show the same rules in the editor;
the template is a prompt, while CI is the authority.
The branch flow
Eight rules, each with what it buys. The sequence that carries them out — every command, in the order it is run, including the several that are easy to type plausibly and wrongly — is the new-feature skill. Follow that file; a change that compiles nothing takes the short lane at the top of it.
| Rule | Why it is one |
A worktree per feature, branched from origin/main; never edit code in the main work tree | Several branches are normally in flight, and one must be able to land while another is mid-edit |
| One commit per subtask, each compiling and passing tests on its own | Nothing is squashed on the way in, so a branch's commits are exactly what main gets, and a bisect lands on one of them |
| A PR per branch, draft from the first commit, body = the subtask list kept ticked | The only view of the branch from outside your worktree, and where CI's own machines grade it (pull_request runs the jobs main gets) |
The PR's checks are the full run; a local make ci-full is for when you expect to be the one who broke it | Two runners, two operating systems, and neither of them is this laptop |
make ci-landing after a rebase that moved the branch, and only then | Its reason is commits replayed onto a base they were never tested against; a rebase that changes no sha produces none |
| The landing closes the PR, not a merge button: force-push after the final rebase so its head is exactly what lands | main then holds that commit and GitHub marks it merged; if they differ, an open PR claims work still in flight |
main is linear — rebase, never merge, and pull with git pull --rebase | A merge commit per branch makes "what landed, when, on top of what" unreadable from git log |
--ff-only is the enforcement, not a formality | If it refuses, the rebase did not happen or origin/main moved. Never reach for a plain git merge to get past it |
Rebase only branches that are yours alone: once pushed, a branch is history other people hold.
Toolchain
Go 1.26+, golangci-lint v2, make, node v22 / npm 10, the gh CLI (authenticated — the branch flow
opens and updates PRs through it), and the wails3 CLI (only needed for GUI builds).
The two version floors above are floors, not a matrix, and the pair CI runs is go1.26.x +
golangci-lint v2.12.2 (.github/workflows/ci.yml; internal/depguardtest names the same lint
version in its skip message). Newer is not automatically compatible, and each way it fails is silent
in a different place:
- golangci-lint v2.12.2 cannot analyse a Go 1.27 toolchain at all. It is built with go1.26 and
panics with
file requires newer Go version go1.27inside package loading, whichinternal/depguardtestreports as "lint failed but not via depguard" — a depguard message for a problem that has nothing to do with depguard. - A newer golangci-lint reports findings CI does not have. v2.13.1 raises
SA4023oninternal/platform/windows.go, correctly:currentUserSIDalways returns an error on the non-Windows build, so thatif err != nilis always true there.make ciis then red locally and green on both runners. make fmtunder Go 1.27 writes formatting CI rejects. 1.27's gofmt aligns a map literal whose longest key stands alone where 1.26's does not, so the documented "runmake fmtafter touching an import block" step introduces agofmtlint failure. It is not visible locally, because the same new gofmt then calls the file formatted.
Reproducing the runners on a machine that has moved ahead needs both halves pinned, and nothing in the repository has to change to do it:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \
| sh -s -- -b "$(pwd)/bin" v2.12.2 # bin/ is gitignored, and depguardtest probes it
export GOTOOLCHAIN=go1.26.0
export AGENTHUB_GOLANGCI_LINT="$(pwd)/bin/golangci-lint"
make ci GOLANGCI_LINT="$(pwd)/bin/golangci-lint"Pinning only the linter is worse than pinning neither: v2.12.2 under a 1.27 toolchain is the panic
above, so GOTOOLCHAIN is not the optional half.
The codex CLI is optional, and only the
security-audit skill looks for it: present, it reviews the
same shards as a second independent engine; absent, that sweep runs single-engine and says so. Not a
build dependency.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

