Imported from waseem484/new-router (
AGENTS.md). Install upstream withnpx skills add waseem484/new-router. Copyright stays with the author.
tools-registry (treg) - guide for every coding agent
treg is the tool catalog for an agent: one base URL, one token, and the agent can call a curated catalog of external endpoints plus its own team's tools without ever holding an API key. The load-bearing mechanic is a proxy that makes the caller's real upstream request, injects the credential server-side when one is required, and relays the answer verbatim. A catalog endpoint explicitly verified as public can instead relay with no injected credential. We never model an upstream API.
Paired treg.to checkout
For work on the hosted treg.to service, clone the public treg repository and private
treg-internal repository as siblings with those exact directory names. When ../treg-internal
exists, treat both repositories as one operational workspace:
tregowns public product code, portable behavior and self-hosting documentation.treg-internalowns live production configuration, operational runbooks, incident evidence and private admin tools.- Read both repositories before changing production behavior, but never copy credentials, live environment exports, customer data or raw logs between them.
- Everything committed here is public, comments and commit messages included: describe treg.to
only by mechanism, never by its numbers, dates, hosts, instance counts or schedules. Those go to
../treg-internal. - Commit and open PRs separately. State the merge order whenever one PR links to or depends on the other.
Do not clone treg-internal inside this repository and do not make it a Git submodule. Start agents
from the repository that owns the task; the sibling path supplies the other half of treg.to context.
Non-negotiables
Everything else in this file is guidance; these are the contract, and they win over any other passage.
- A team's own key always wins over treg's, is never metered, and is never routed or overflowed.
- A hold (the balance
reservesets aside for one call) is settled or released exactly once, on every path: timeout, cancellation and exceptions included. - A request holds zero database connections while upstream or object-storage I/O is in flight.
Keep
reserveandsettleseparate; read archive pointers, close the session, then fetch bytes. - Plain
/call/is a faithful relay: the injected credential, the transport headers listed insrc/treg/infra/upstream/relay.py, and (on treg's shared key only) the per-org and, for pinned agents, per-pin re-scoping of the caller'sIdempotency-Keyare the only rewrites. A credential binding withlocation: "json"explicitly parses and reserializes the top-level JSON object; it is not byte-faithful and must never be used with an upstream that signs or hashes the raw body. Never add upstream-specific modeling. A live-verified free catalog endpoint may declare an anonymous fallback; its empty binding list omits credential injection but does not strip or rewrite caller headers. Routed endpoints and overflow wrap the child's answer and say so; they never alter it. Responses needing settlement or ownership evidence are buffered by the application up to 8 MiB; exceeding that limit fails without charging, never returns a successful prefix. Authorized free final fetches needing no body evidence stream in full. - Balances change only through money's five entries: grant, topup, reserve, settle, release. There is deliberately no refund or adjustment entry; an ops correction is a grant.
Changing any invariant in this file means editing this file in the same PR. Routed endpoints and overflow once shipped with every other doc updated while this file still said "no router"; agents then built against a constitution that was wrong.
Where the truth lives
- Design docs are fragments.
docs/context/holds one per subsystem, each naming itssrc/treg/*sources in frontmatter;docs/context/README.mdis the generated index anddocs/context/foundation/charter.mdthe start. Read the fragment before changing an area (thetools-registry-contextskill in.agents/skills/loads it). - Before pushing:
bash .agents/skills/tools-registry-context/scripts/drift.shmaps changed sources to fragments. Update them and commit the docs in the same commit as the code. - Agent-facing files are the product's front door, not documentation:
src/treg/web/llms.txtandsrc/treg/web/skill.md(installed into every agent byinstall.sh). They,README.mdand this file must agree on how treg works; a behavior change asks whether all four move. - Three files move together or they drift:
src/treg/web/tutorial.js(the only interactive source) and its hand-kept prose mirrorssrc/treg/web/tutorial.mdanddocs/TUTORIAL.md. README.mdis the overview and quickstart,USAGE.mdthe CLI reference,CONTRIBUTING.mdthe dev setup,SECURITY.mdrequired reading before touching the proxy, runners, auth or secrets.- If available,
../treg-internalholds private operational configuration and tools; read itsREADME.mdbefore changing production settings. Public development does not depend on it.
Architecture
Four layers
routers/ -> application/ -> domain/ -> infra/. Imports point inward only.
| Layer | Owns | Never |
|---|---|---|
routers/ |
HTTP and MCP translation in, response shape out | business rules, query orchestration, money |
application/ |
use-case sequencing, transaction boundaries, compensation, cross-domain composition | empty wrappers around one-domain CRUD |
domain/ |
rules explainable and testable alone: identity, governance, connections, tools, catalog, capacity, money, asynctasks, feedback |
routers, application, concrete SDKs |
infra/ |
DB engine and sessions, crypto, upstream relay and SSRF, ratestore, the shared key-value store, email, Stripe | decisions |
- Domains do not import each other, with three sanctioned edges:
governance -> identity,tools -> connections,capacity -> catalog(read-only).identityandmoneyare leaves. import-linter enforces the layering ([tool.importlinter]inpyproject.toml, run by CI);docs/context/architecture/import-boundaries.mdexplains each contract. bootstrap.pyalone knows concrete implementations.api.pyis the legacyall-role entrypoint, not where logic goes.audit.pyis best-effort and drops rows under load, so nothing that must persist goes through it;analyticsis read-only.
Writes
- Session discipline. The application use case opens the session and is the only place that
commits; domain functions never commit or roll back. A commit mid-flow silently breaks
compensation, and no import rule can catch it. Money's public
reserve,settleandreleasecommit by design; a few other domain commits remain. Do not add another; move one out when you touch it. - Table ownership. One writer module per table; cross-domain reads are fine. Three recorded
exceptions: only money writes
org.balance_micro, the daily-spend counter (spent_today_*) and the auto-top-up fields; the call runtime may persist an OAuth token refresh intosecret; audit writescallrecord, domains only read it. - Feedback handling. This repo owns
FeedbackHandlingandFeedbackHandlingEventmodels and migrations; the private admin service is their only runtime writer. Original reports remain owned by the feedback domain. Seedocs/context/architecture/feedback.md. - The call runtime is self-contained.
src/treg/application/call/depends on no management code (routes, login, OAuth consent, Stripe top-up), reads only membership, deny rules, credentials, catalog prices and balances, and writes only whattests/test_call_architecture.pyallowlists (the ledger entries, idempotency claims, OAuth refresh, audit and telemetry, first-call markers, tag budgets, capacity marks, overflow spend, the member's daily-cap slot, the per-team archive-question marks, and durable provider-resource ownership). Extend the test's allowlist in the same PR as any new write, and expect the reviewer to ask why. - Signup credit. Once per new verified user, enforced by a user-level atomic claim committed with the grant. Team deletion never restores eligibility; legacy registration is not email proof.
- Money. Everything is integer micro-USD - never floats, never cents. The Stripe SDK lives
only in
infra/stripe.py, orchestration inapplication/billing.py, andreconcile.pyis read-only. Seedocs/context/architecture/money.md. - The archive serves every tier, keyed by whose question it is. Own-credential answers are
recorded (bounded read, never a prefix) under an org-scoped key, or a connection-scoped key on
an
own_accountendpoint, and reach other teams only where the endpoint itself declarescache.sharing: public; a provider's storage licence never decides that. A hit on an own key is free; a metered hit settles through the same hold, atarchive_hit_repeat_price_percentonce the team has paid for that question. Seedocs/context/architecture/archive.md.
Security guards that look redundant on purpose
expose_dev_code (dev OTP only on a local sqlite database, config.py), the call-time SSRF check
(infra/upstream/ssrf.py), the fail-loud missing-Fernet-key check in verify_db, and the
treg run allow-list and rlimits (runner.py). Read the fragment before touching any of them.
Development
uv run --with pytest-xdist pytest -n auto -q # daily local default (same shape as CI)
uv run --frozen python -m pytest -q # serial: debugging one test, or order
uv run treg --help # the CLI from this checkout
uv run python -m treg # the server
uv run lint-imports # the import-linter contracts (CI runs this too)
scripts/dev-local.sh up # live dev stack on :18790 with its own sqlite DB
xdist is pulled via --with, not the lockfile — same as CI. The Postgres CI job
(test-postgres) must stay serial: every worker would share one database while
reset_db() drops tables.
- Dependencies change through
uv addoruv lock, never by hand.pyproject.tomlpinsrequired-versionso an old uv refuses to run instead of rewritinguv.lock; CI uses--locked. - The package is split. The base install is the light CLI; the FastAPI/DB stack is the
[server]extra, the certificate authority is[proxy]. Never import a heavy dependency at the top of a CLI-path module; the "Lightweight CLI modules" import-linter contract lists them and fails the build. - Frontend rollout.
frontend/README.mddocuments account assignment and rollback.src/treg/web/dashboard-legacy/is deprecated, retained only for temporary rollout and rollback. Never hand-edit it or mirror new features/fixes into it;frontend/is the only maintained Dashboard source. Follow the retirement checklist infrontend/README.mdto remove it after rollout, including anonymous entries that still use legacy at 100%. - The dashboard lives in
frontend/(Vue components, TypeScript entry/transport, Vite). Build withbash scripts/build-dashboard.sh; generated assets insrc/treg/web/dashboard/ship with Python. Runnpm --prefix frontend testandnpm --prefix frontend run test:e2e. Existing Options API use cases live infrontend/src/state/; preserve their session and navigation behavior when narrowing component state. Never put dashboard logic back into HTML. Manage third-party browser libraries through pinned npm packages or version-pinned CDN URLs; do not commit copied library builds. Keep critical app runtimes available from the npm build. - Schema. Alembic owns it (
src/treg/alembic/versions/); every schema change is a revision. Startup only verifies the revision and refuses to boot when behind; migrations run only viapython -m treg upgrade.
Working agreement
- Keep the suite green; add tests for new behavior. Conventional Commits (
feat(scope): ...,fix: ...,docs: ...); one logical change per commit; the PR says what changed and why and names the fragments it updated. /mcp/and/mcp/v2/differ on purpose. A change to either or to shared MCP code is reviewed against both; do not unify them in passing.- A catalog data PR is a few rows and a PR body. Cache admission, comparison declarations,
adapters and contracts are rows in
src/treg/catalog/; each declaration carries a one-line reason and nothing more. The evidence (traffic, byte sizes, change observations, bodies read) goes in the PR body, never into a fragment or a comment. A fragment moves only when a mechanism changes;drift.shnaming one is a prompt to check it, not an obligation to write. No dated per-provider sections indocs/context/architecture/catalog.md: a provider's quirk lives on its row as anote. No per-endpoint tests: the round-trip test over every shipped adapter and the validator already judge the rows, and a test that restates a list of declarations is deleted, not extended. Code that a data PR needs is its own PR, merged first.
When writing user-facing copy
One concept, one word. Settled deliberately - mixed vocabulary is how the old framing creeps back.
| Thing | Word |
|---|---|
| what an agent calls | a tool |
| the public half | the catalog |
| the team's half | your own tools (your keys and skills) |
| the server itself | registry, and only for that |
Do not call either half a vault, a marketplace, or the registry. Say what the agent can now do, not what we store. Never use a count of endpoints or providers in this file; the catalog changes weekly and every stale number is a lie.
Do not document what is not built. An agent that believes a feature exists fails in a way nobody can debug. Provider choice is the easiest thing to overstate: treg compares providers, and chooses only in the two disclosed cases of non-negotiable 4.
