-
Notifications
You must be signed in to change notification settings - Fork 0
Extending MyACE
Task-oriented guides for common changes. Each one assumes you've read architecture.md and, if you're touching auth, the Authorization invariants.
To support a new framework (say, "pi.dev" — see
adapters-research.md for other unbuilt candidates;
don't reuse one of the 11 already shipped in
backend/app/adapters/__init__.py):
-
Backend: create
backend/app/adapters/pi_dev.pyimplementingBaseAdapter(seebackend/app/adapters/base.pyfor the interface):Register it inclass PiDevAdapter(BaseAdapter): def adapter_name(self) -> str: return "pi-dev" def supported_targets(self) -> list[str]: return ["pi-dev"] def translate(self, artifacts: list[CanonicalArtifact]) -> dict[str, str]: ... # canonical artifacts in, {filename: content} out
backend/app/adapters/__init__.py's adapter list. -
Compile endpoint: add the adapter's primary name to the
targetLiteralinProfileCompileRequest(backend/app/models/profile.py). This is easy to forget and fails silently in a confusing way: the adapter works fine in isolation, butPOST /profiles/compile422s on it forever because Pydantic rejects the request before it ever reachesget_adapter()— this exact bug shipped for four adapters (codex-cli,copilot-cli,cline,windsurf) until it was caught and fixed. -
CLI copy (optional, currently unused): mirror the same adapter in
cli/myace_cli/adapters/pi_dev.py. This package exists — and is maintained in sync forclaude_code/opencode/cursor— for a local -rendering fallback that hasn't been wired up yet:myace pull(cli/myace_cli/sync.py) always calls the backend's/profiles/compileendpoint directly and has no code path that falls back tomyace_cli.adaptersif the server is unreachable. Until that's built, adding a CLI copy for a new adapter is optional — the other eight backend adapters (everything exceptclaude_code/opencode/cursor) don't have one, and nothing currently regresses because of that. If you do add one, keep it in sync with its backend counterpart by hand — there's no automated check for this today. -
Test: add a
TestPiDevAdapterclass tobackend/tests/test_adapters.pyfollowing the existing pattern (seeTestClaudeCodeAdapter,TestCodexCliAdapter,TestWindsurfAdapter, etc.) — at minimum, one test per artifact type your adapter handles specially. -
Docs: add a row to the target adapters table in
README.md, and to the adapter list inarchitecture.mdandCLAUDE.md.
Adapters must stay stateless and pure — see
invariants.md #9. Don't reach into the
database or filesystem from inside translate().
The five artifact types (rule, skill, agent, workflow,
model_config) are a closed set referenced in a lot of places. Adding a
sixth means touching all of:
-
backend/app/models/artifact.py— nothing to change in the model itself (artifact_typeis a plain string), but update the docstring/comment. - Every adapter's
translate()— decide how the new type renders for each target framework, including "doesn't apply here, skip it." -
backend/app/services/scanner.pyandcli/myace_cli/scanner.py— how does this type get discovered from a directory scan? Update both (see the scanner-duality rule inAGENTS.md). -
backend/app/services/github_export.py'sartifacts_to_files()— the inverse of step 3. Keep it symmetric (invariant #10). -
frontend/src/types/index.ts'sArtifactTypeunion, and anywhere the frontend special-cases type (e.g.CollectionDetail.tsx'sARTIFACT_TYPESfilter list andtypeColorsmap).
OIDC (generic), GitHub, and Google are already wired via Authlib
(backend/app/core/security.py). Credentials for all three can be set
either via .env or, since the OAuth provider admin UI shipped, via System
Settings → Authentication Providers — click a provider row to expand it,
which shows the exact callback URL to register with the provider and fields
for Client ID/Secret (and Issuer URL/Scopes for OIDC). A "Test Connection"
button checks reachability/format (not a full login — that needs a real
browser redirect, hence the "Sign in with X" callout in its result message).
See ADR-0006 for how the
secret is stored.
To add a fourth provider that also speaks OAuth2/OIDC:
- Add
<provider>_client_id/<provider>_client_secret(and any provider-specific URLs) tobackend/app/core/config.py, and matching{provider}_client_id/{provider}_client_secret_encryptedcolumns toSystemSettings(+ a migration) if it should also be admin-editable. - Extend
get_effective_oauth_config()(backend/app/services/effective_settings.py) with the new provider's branch, and add it toget_oauth_client()'s registration branches insecurity.py, following the GitHub/Google examples. - Add
"<provider>"to the allowed-providers checks inbackend/app/api/auth.py'slogin()/auth_callback()routes and toOAUTH_PROVIDERSinbackend/app/api/admin.py. - Add it to
GET /auth/providers's response and to the frontend'sAuthProviderstype (frontend/src/types/index.ts),Login.tsx's button list, andPROVIDER_INFOinSystemSettings.tsx(setup steps + console/docs links for the credentials accordion). -
If the provider isn't OIDC (like GitHub — plain OAuth2, no
.well-known/openid-configuration), you must setapi_base_url/userinfo_endpointexplicitly in itssecurity.pyregistration (see AGENTS.md's Security Rules), and add a provider-specific branch inauth_callback()to map its actual response fields — don't assume the generic OIDCsub/email/name/pictureclaim names apply. Also check whether the provider ever omits an email by default (GitHub does, even with an email-scoped grant) and needs a follow-up call to fetch one.
No database migration needed for users — User.oidc_provider is a plain
string, not an enum.
Password-reset emails (POST /auth/forgot-password) are sent via SMTP,
configured either through .env (SMTP_HOST/SMTP_PORT/SMTP_USERNAME/
SMTP_PASSWORD/SMTP_FROM_EMAIL/SMTP_FROM_NAME/SMTP_USE_TLS — see
.env.example) or through System Settings → Email (SMTP), which an admin
can use instead of editing .env and restarting. A value saved via System
Settings overrides the matching env var at runtime
(backend/app/services/effective_settings.py); the master smtp_enabled
toggle there has no env-var equivalent — it defaults off, so email sending
stays inert until an admin explicitly turns it on.
Requires SETTINGS_ENCRYPTION_KEY (.env.example) to be set before the
SMTP password can be saved via the UI — see
ADR-0006. Use the "Send Test
Email" button on the System Settings page (Settings → System) to validate a configuration (host/
port/credentials as currently typed, not necessarily saved yet) before
relying on it — it sends a real email to the requesting admin's own address.
Every new route that touches user data must answer three questions before it's done:
-
Does it need
Depends(get_current_user)? Almost always yes — see invariants.md #1. The only routes that skip it are the explicit public list in that same invariant. -
What's the access rule? Pick one and implement it with the shared
helpers (
backend/app/core/authz.py), don't hand-roll a check:- Owner-or-admin, read:
authorize_access(owner_id=..., current_user=..., is_public=...) - Owner-or-admin, write: same, with
write=True - List endpoint:
owner_or_public_clause(...)folded into the query
- Owner-or-admin, read:
-
Does it touch more than one resource? If it reads from one resource
and writes to another (like
bulk_export_artifacts's source/target collections), each one needs its own check — invariants.md #6.
If the route is security-relevant, add a scenario to the verification list
this project has used historically (ask in your PR if you're not sure
where that lives for your change — see
CONTRIBUTING.md).
Same as above, but explicitly: write a mental (or literal, in tests) table
of every resource the operation touches and what access level each needs.
bulk_export_artifacts is the reference example — its source collection
needs read access, its target collection (if it already exists) needs an
independent write check, and a brand-new target collection needs no check
(ownership is just assigned to current_user.id). Don't assume checking
the "primary" resource in the URL path covers every resource the handler
actually reads or writes.
mypy --strict currently runs in CI as advisory only (it doesn't block
merges) — see debugging.md.
Most of the backlog is missing return-type annotations on FastAPI route
handlers (mechanical, safe to add incrementally) plus a handful of genuine
SQLModel/SQLAlchemy-vs-mypy limitations around class-level column access
(Column.in_(), generic Result inference) that need either targeted
# type: ignore[...] comments with a reason, or a proper SQLAlchemy mypy
plugin setup — don't "fix" these by changing working query code (see the
same debugging entry). This is a good, low-risk first contribution: pick a
handful of functions, add return types, confirm mypy app complains less,
open a PR.
- New pages go in
frontend/src/pages/, added to both the route list inApp.tsxand the sidebar inLayout.tsxif they need direct navigation. - New API calls go in
frontend/src/lib/api.ts, typed againstfrontend/src/types/index.ts— never callfetch()directly outside this file (see the credentials gotcha in debugging.md). - If two components fetch the same resource with different filters, give them distinct React Query keys — debugging.md.
-
Layout.tsx's sidebar is alg:-breakpoint responsive drawer: static and always visible atlg(1024px) and up, an off-canvasfixedpanel below it (toggled by aMenu-icon button in a mobile-only top bar, closed by tapping the backdrop, its ownXbutton, or navigating). New pages don't need to do anything special for this — content just renders in<main>below/beside it — but if a page adds its own wide content (a table, a multi-column form), wrap it so it doesn't cause horizontal scroll at 375px — anoverflow-x-autowrapper (seeSystemSettings.tsx's tables) or agrid-cols-1 sm:grid-cols-2pattern are the two used elsewhere in this codebase.
Generated from docs/ by scripts/sync_wiki.py. Back to repo
- Home
- Architecture
- Data Model
- Invariants
- Extending MyACE
- Debugging
-
ADR Index
- ADR-0001-canonical-ir-as-markdown-with-frontmatter
- ADR-0002-session-cookie-auth
- ADR-0003-ownership-based-authorization
- ADR-0004-github-export-via-rest-api
- ADR-0005-email-password-baseline-auth
- ADR-0006-encrypted-admin-editable-secrets
- ADR-0007-additive-user-role-column
- ADR-0008-collection-moderation-state-machine
- ADR-0009-manifest-based-drift-detection
- ADR-0010-structured-handoff-field
- ADR-0011-public-demo-sandbox
- ADR-0012-manual-collection-freshness-verification
- ADR-0013-post-hoc-unpublish
- Adapter Research