add-component
Skill144 starsChanged 15 months agoMITView on GitHub
Use when adding a new infrastructure component (worker, scheduler, database, redis, ingress, observability) to the Aegis Stack framework itself, or adding a new variant axis (like worker_backend or database_engine) to an existing component. Covers the plugin-spec registry, dependency resolution, copier questions, generation/update plumbing, template, test, docs, and i18n files that must change together.
- Reads credentials
We split this skill into the rules it gives an agent
13 rules. 0 are common, 1 is distinctive, and 12 are very specific to this project.
0
Common
Most of the 7 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Do NOT use for adding a service (business logic: auth, AI, blog, finance, payment, comms, insights) - services live in …
- Very specific: line 63
every path the component owns.
- Very specific: line 191
If the component needs a whole file of its own, put it in the spec's FileManifest instead (see "Files this component owns" above).
- Very specific: line 199
Leave docs_path="" until the page exists.
- Very specific: line 239
Write the failing test first: add a StackCombination to tests/cli/test_stack_generation.py::STACK_COMBINATIONS asserting the new …
- Very specific: line 243
Add ComponentNames.<NAME> (+ INFRASTRUCTURE_ORDER entry) and AnswerKeys.<NAME> in aegis/constants.py.
- Very specific: line 246
Add the ComponentSpec entry in aegis/core/components.py: identity fields, required_components, pyproject_deps, docker_services, …
- Very specific: line 250
Add the include_<name> question to copier.yml (and the axis question with choices/when if applicable).
- Very specific: line 261
If the component ships dashboard UI, wire the card/modal and their __init__.py.jinja exports (mirror the service pattern in add-service).
- Very specific: line 263
Add the i18n keys (component.<name>, component.<name>.long, plus prompt.<component>_<axis> / choice.<component>.<value> for a variant axis) …
- Very specific: line 267
Add the tests/cli/test_stack_generation.py combination (from step 1, now passing), the tests/cli/conftest.py cache entries per variant, and …
- Very specific: line 270
If any generated-project fix happened in a scratch/prototype project rather than directly in the template, backport it into …
- Distinctive: line 276
Run the gates below and fix anything red.
“Common” is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Add component
Adds a new infrastructure component (background worker, scheduler, database,
redis, ingress, observability) to the framework so generated projects can
select it via aegis init or add it later via aegis add, OR adds a new
variant axis to an existing component (a second/third backend choice like
worker_backend on worker or database_engine on database). Traced from
worker (with its worker_backend axis: arq/dramatiq/taskiq) and database
(with its database_engine axis: sqlite/postgres).
When to use
Use for either:
- A brand-new optional component (infrastructure, not business logic) behind
its own
include_<name>copier question, addable/removable viaaegis add/aegis remove. - A new variant axis on an existing component: a second copier question
(
<component>_<axis>) that changes which files render for that component, following theworker_backend/database_enginepattern.
Do NOT use for adding a service (business logic: auth, AI, blog,
finance, payment, comms, insights) - services live in
aegis/core/services.py and share the same PluginSpec shape but a
different file footprint and CLI surface (aegis add-service, not
aegis add); use the add-service skill instead. Do NOT use for changing
behavior inside an existing component with no new question or file gating;
that is ordinary feature work.
Files that change
Registry (ComponentSpec is a thin alias of PluginSpec pinned to
kind=COMPONENT; see aegis/core/plugins/spec.py):
aegis/core/components.py: add aComponentSpecentry to theCOMPONENTSdict. Setcategory(its concern:PartCategory) and, where its health check is named differently from the spec (redisiscache),health_name. Its place on Overseer's map and sidebar is declared there too:tier(0 edge, 1 app, 2 data; none runs in the webserver),page(where not its health name),role(store,bucket),depends_on(the map's lines, by health name),consumes,hosts,holds_keys,order. Then runmake parts, which writes every spec's into the template'sapp/core/parts.py(tests/core/test_parts_file.pyfails while stale). Settype(ComponentType.INFRASTRUCTURE;ComponentType.COREis reserved forbackend/frontendinCORE_COMPONENTS- core components are never prompted, never removable, and skipped by cleanup entirely),description/long_description,required_components(hard deps, e.g. worker requires["redis"]),recommended_components,pyproject_deps,docker_services(compose service names this component owns, used by stack-generation tests),template_files(display list),marker_path(the path used to detect the component on disk - forreconcile_answers_from_diskand dev-mode cleanup context),docs_path(see docs section below), andmigrations=[...]only if the component owns tables (seeSCHEDULER_MIGRATIONinaegis/core/migration_generator.py: the spec names the revision and its Postgres schema, nothing more - what the revision creates comes from the component's SQLModel classes, for every non-memory backend).aegis/core/components.py:files=FileManifest(primary=[...], extras={...})- every path the component owns.
primaryis the always-on add/init base;aegis/core/post_gen_tasks.py'sget_component_file_mapping()derives from every spec's manifest viacompute_file_mapping()(aegis/core/file_manifest.py), so there is no separate hand-maintained mapping to edit. Useextrasfor file groups that only render real content for a specific variant value (e.g. scheduler's"scheduler_persistence"group, gated onscheduler_backend != "memory") - keeping them out ofprimarystopsaegis addfrom writing 0-byte stubs when the parent variant doesn't need them;aegis removestill deletes them viaget_component_files(..., full=True).
- every path the component owns.
aegis/constants.py:ComponentNames- add<NAME> = "<name>"and append it toINFRASTRUCTURE_ORDER(controls interactive prompt ordering).AnswerKeys- add<NAME> = "include_<name>". For a new variant axis, add the axis key (e.g.<COMPONENT>_<AXIS> = "<component>_<axis>") and, if the axis has a fixed enum of values, a dedicated<Axis>Backends/<Axis>Typeclass nearWorkerBackends/StorageBackendswith anALLlist.aegis/core/dependency_resolver.py: no edits needed for a plain component - it readsComponentSpec.requires/.recommends/.conflictsgenerically viaCOMPONENTS. Only add logic here if the new component needs a resolution rule beyond simple hard-dependency (worker -> redis is handled byrequired_componentsalone, no custom code).copier.yml(repo root, not undertemplates/): add theinclude_<name>bool question. For a variant axis, add<component>_<axis>withchoices:andwhen: "{{ include_<component> }}"- UNLESS the value must survive even when the component is off (comparedatabase_engine, which has nowhenclause because templates gate on it regardless ofinclude_database, e.g. a persistent scheduler needs to know the engine before database is even selected).aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja: addinclude_<name>: {{ include_<name> }}. For an axis gated bywhen, adding the line here is NOT required for correctness - Copier omitswhen-gated fields from the rendered answers file (observed forworker_backend,scheduler_backend,include_oauth; none of them appear in this file today), andcopier_manager.py's post-generation patch (below) backfills everycopier_datakey Copier dropped. Add the line anyway for axes that are NOTwhen-gated (likedatabase_engine, which IS listed here) since those are the ones templates read unconditionally.
Generation and update plumbing (thread the new AnswerKeys.<NAME> /
AnswerKeys.<COMPONENT>_<AXIS> answer key through every one of these; a
component that skips them generates fine at aegis init but breaks
aegis add/aegis remove/aegis update on an existing project):
aegis/core/template_generator.py(TemplateGenerator.__init__andget_template_context()): extract the axis value from bracket syntax (worker[dramatiq]) the same wayself.worker_backendis derived fromextract_engine_info(component), default it (WorkerBackends.ARQ), and add it to the returned context dict. Also branch in the pyproject-deps loop (base_name == ComponentNames.WORKERblock) if different variant values pull different pip packages.aegis/core/copier_manager.py(generate_with_copier): addAnswerKeys.<NAME>: template_context[AnswerKeys.<NAME>] == "yes"(or.get(..., default)for optional flags) to thecopier_datadict passed torun_copy. This is also where the backfill described above lives - every non-underscorecopier_datakey gets written into.copier-answers.ymlafter generation if Copier dropped it, so this dict is the actual source of truth for what ends up in the answers file, not the.copier-answers.yml.jinjalist.aegis/core/manual_updater.py(ManualUpdater):get_copier_defaults()(fromaegis/core/component_files.py) merges copier.yml defaults under the persisted answers at construction time, so a variant axis with adefault:in copier.yml needs no extra wiring here for the default case. Butadd_component/remove_componentmust referenceAnswerKeys.<COMPONENT>_<AXIS>explicitly anywhere the render context needs it - as of this trace,manual_updater.pyhas zero references toworker_backend, which is whyaegis add worker[dramatiq]on an existing project silently ignores the bracket and always adds arq (the default). Don't repeat this gap for a new axis: thread it throughadd_component's context building, mirroring howaegis/commands/add.pythreadsAnswerKeys.DATABASE_ENGINE/AnswerKeys.SCHEDULER_BACKENDintocomponent_databefore callingupdater.add_component(component, component_data).aegis/commands/add.py(add_command): parse the variant out of bracket syntax viaextract_engine_info(comp)for the new component (mirrors the scheduler-backend block), validate it against the axis'sALLlist, and populateupdate_data[AnswerKeys.<COMPONENT>_<AXIS>]/ the per-componentcomponent_datadict passed toupdater.add_component(...).aegis/commands/update.py: no per-component AnswerKeys threading exists here today (verify this still holds before skipping it) -aegis updateruns the full Copier update flow and re-derives context from the project's own (reconciled) answers file, not from a hand-maintained key list.aegis/cli/guided.py/aegis/cli/interactive.py: add achoose_<component>_backend(self) -> strmethod (on the guided-setup UI class) followingchoose_worker_backend()/choose_database_engine()- a list of_Choice(value, label, description)tuples fed toself._select(...)with an i18n-backed prompt string (_g("prompt.<component>_backend", "...")) and per-choice description keys (choice.<component>.<value>).
Template side (aegis/templates/copier-aegis-project/{{ project_slug }}/):
app/components/<name>/: the component's own package.docker-compose.yml.jinja/.dev/.prod: gate services behind{%- if include_<name> %}(full-body wrap, never a Jinja expression inside a YAML value Copier can't parse standalone). These compose templates carry no inline comments - which components a given project selects varies per stack, so a comment written for one combination misleads for another; state conditions through the Jinja gate itself, not prose beside it.Makefile.jinja: gate any newmaketargets the component needs (compare howworker/schedulertargets are conditioned).app/components/backend/startup/component_health.py.jinja: register the health check behind{%- if include_<name> %}, following the existinginclude_worker/include_databaseblocks. For a variant axis, nest a second{%- if <component>_<axis> == "value" %}inside.pyproject.toml.jinja: gate the component'spyproject_depsbehind{%- if include_<name> %}. For a variant axis with per-value deps (arq vs dramatiq vs taskiq each pull different packages), branch on the axis value inside that same gate.docs/components/<name>/index.md(+configuration.md,examples.md,extras/<topic>.mdas applicable - this is the multi-page conventionworkerandscheduleruse; a singledocs/components/<name>.mdfile also satisfies the docs_path test, matchingdatabase/scheduler's top-level file). Wire the new pages into the rootmkdocs.ymlnav under theComponents:block.- Tests (generated-project template tests):
tests/components/test_<name>.py, and API/frontend tests if the component ships routes or dashboard cards/modals (app/components/frontend/dashboard/cards/<name>_card.py,modals/<name>_modal.py, exported from the matching__init__.py.jinjafiles, same wiring shape as the service card/modal pattern inadd-service).
Cross-cutting:
- Shared files need no registration. A shared file is any template path
no component/service
FileManifestclaims;aegis add/removefinds it by rendering the tree at the old and new answers and diffing. Put{% if include_<name> %}conditionals indocker-compose.yml,pyproject.toml,Makefile,component_health.py,app/core/config.py,.env.example— or in a brand-new shared file — and it is handled automatically. There is no list to add it to.- If the component needs a whole file of its own, put it in the spec's
FileManifestinstead (see "Files this component owns" above). That is the only file declaration there is. tests/core/test_shared_scope_completeness.pyfails, naming the file, if a stack-dependent file somehow ends up handled by nothing.
- If the component needs a whole file of its own, put it in the spec's
docs/components/<name>/index.md(or<name>.md) is pinned bytests/core/test_spec_docs_paths.pyoncedocs_pathis set on the spec- it accepts either
docs/<docs_path>.mdordocs/<docs_path>/index.md. Leavedocs_path=""until the page exists.
- it accepts either
i18n:
aegis/i18n/locales/en.py: add"component.<name>"(short description, shown byaegis add --helpcomponent listings and the guided setup) and"component.<name>.long"(long-form description) with real English strings. For a variant axis, also add"prompt.<component>_<axis>"and one"choice.<component>.<value>"key per choice (seechoice.worker.arq/choice.worker.dramatiq/choice.worker.taskiq).aegis/i18n/locales/{de,es,fr,ja,ko,ru,zh,zh_hant}.py: add the SAME keys to every other locale, using the English string as a placeholder. This is required for parity:tests/core/test_i18n.pyasserts every locale carries the same keys as English (onetest_<locale>_has_all_keysper locale) and fails naming the missing key otherwise. Real translation is a separate translator-reviewed pass; do not attempt it here, but the keys must exist now.
Tests (this repo's own CLI/core test suite):
tests/cli/test_stack_generation.py: add aStackCombinationentry toSTACK_COMBINATIONS(mirrors theworker/scheduler/fullentries) withexpected_files,expected_docker_services, andexpected_pyproject_deps.tests/cli/conftest.py: add a<name>_with_<variant>entry toNAMED_PROJECT_SPECSfor each new variant value (seebase_with_worker_taskiq/base_with_worker_dramatiq) so downstream tests copy a cached project instead of regenerating one.tests/cli/test_add_command.py: add coverage foraegis add <name>(and, if applicable,aegis add <name>[<variant>]) following the existingtest_add_scheduler_with_backend_flag_sqlite/test_add_worker_auto_adds_redisstyle. There is no dedicated "importable regen guard" test for components analogous toadd-service'sSERVICE_INVOCATIONS- add explicit add-path assertions here instead so a missingFileManifestentry surfaces as a test failure rather than a stub file in the wild.tests/core/test_spec_docs_paths.pyandtests/core/test_i18n.pyneed no edits - they parametrize overCOMPONENTS/SERVICESand the locale catalogs automatically; they just need to pass.
Procedure
- Write the failing test first: add a
StackCombinationtotests/cli/test_stack_generation.py::STACK_COMBINATIONSasserting the new component's expected files/docker services/deps. Confirm it fails for the right reason (component doesn't exist yet). - Add
ComponentNames.<NAME>(+INFRASTRUCTURE_ORDERentry) andAnswerKeys.<NAME>inaegis/constants.py. For a variant axis, add the axis key and its<Axis>Backends-style enum class. - Add the
ComponentSpecentry inaegis/core/components.py: identity fields,required_components,pyproject_deps,docker_services,marker_path,files=FileManifest(primary=[...], extras={...}), andmigrations=[...]if it owns tables. - Add the
include_<name>question tocopier.yml(and the axis question withchoices/whenif applicable). Add the corresponding line to.copier-answers.yml.jinjafor any NOT-when-gated field;when-gated fields are backfilled automatically (see Files that change). - Thread the new answer key(s) through the generation/update plumbing:
template_generator.py,copier_manager.py,manual_updater.py,aegis/commands/add.py(bracket parsing +component_data), and the guided/interactive prompt classes (guided.py/interactive.py). - Build the template package:
app/components/<name>/, docker-compose gating (no inline comments),Makefile.jinjatargets,pyproject.toml.jinjadeps, and the health check registration incomponent_health.py.jinja. - If the component ships dashboard UI, wire the card/modal and their
__init__.py.jinjaexports (mirror the service pattern inadd-service). - Add the i18n keys (
component.<name>,component.<name>.long, plusprompt.<component>_<axis>/choice.<component>.<value>for a variant axis) toen.py, then stub the same keys into every other locale for parity;tests/core/test_i18n.pyfails otherwise. - Add the
tests/cli/test_stack_generation.pycombination (from step 1, now passing), thetests/cli/conftest.pycache entries per variant, andtests/cli/test_add_command.pyadd-path coverage. - If any generated-project fix happened in a scratch/prototype project
rather than directly in the template, backport it into
aegis/templates/before finishing - templates are the source of truth. - Once the docs page exists, add
docs/components/<name>/(or<name>.md), wire it into the rootmkdocs.ymlnav, and setdocs_pathon the spec. - Run the gates below and fix anything red.
Gates
make check(lint, typecheck, test) must pass.make test-stacks-quickwhile iterating (fast feedback: runs the stack validation phase against a representative subset - base, everything, insights).make test-stacks-fullbefore calling the work done (runs the generation-onlytest-stackspass, the slowtest-stacks-buildvalidation pass, and the kitchen-sinkeverythingstack).
Pitfalls
- Template changes render from the committed git state unless you pass
aegis init --dev(working tree) or commit first; testing a template edit with plainaegis initon an uncommitted change silently uses stale content. - Copier conditional filenames are banned (broke on Windows); gate content
with a full-body
{% if %}...{% endif %}wrap inside the file instead of naming the file conditionally. copieris pinned below 9.15 inpyproject.toml(9.15+ relocates.copier-answers.yml, breakingaegis update); a dependabot ignore rule guards the pin - do not "fix"/bump it even if a bot PR proposes it..copier-answers.yml.jinjalooks incomplete on purpose: fields gated by a copier.ymlwhen:clause (likeworker_backend,scheduler_backend,include_oauth) are omitted from it, because Copier itself drops conditional answers on render. Don't "fix" this by adding them - the actual persistence path iscopier_manager.py's post-generation backfill, which patches anycopier_datakey Copier dropped directly into the written.copier-answers.yml.FileManifest.primaryon the spec is the single sourceget_component_file_mapping()derives from. A file present in the template but missing fromprimaryrenders correctly on freshinit(Copier just includes it) but is silently skipped byaegis add/aegis removeon an existing project - there is no equivalent toadd-service'sSERVICE_INVOCATIONSregen-gap guard for components today, so cover this with explicittests/cli/test_add_command.pyassertions instead.- The worker backend suffix-rename in
post_gen_tasks.cleanup_components(_rename_backend_files/_remove_backend_files) is the concrete model for a variant axis that ships parallel source files (registry_dramatiq.py,registry_taskiq.py, canonicalregistry.py): at generation time it renames the chosen backend's*_<backend>.pyfiles to their canonical names and deletes the other backends' files. Skipping this for a new axis leaves all variants' files on disk simultaneously, each importing modules that shadow each other. - The same function distinguishes init (backend-specific source files
still present, e.g.
*_dramatiq.py) from update (only canonical files remain from a prior run) by checking whether any*_<backend>.pyfiles exist before renaming. Running the arq-cleanup unconditionally on update would delete the canonical files a previous run already renamed into place, which is the exact bug the init-vs-update branch exists to prevent; a new variant axis must preserve it. aegis/commands/manual_updater.py'sadd_componenthas no knowledge ofworker_backendtoday:aegis add worker[dramatiq]on an existing project always installs the arq default, silently discarding the bracket value. Don't assume bracket syntax "just works" for a new axis on the add path - verify (and if needed, add) the threading inadd.pyandmanual_updater.pyexplicitly; it is not inherited for free from theaegis initpath.docs_pathon the spec is pinned bytests/core/test_spec_docs_paths.pyto a real file under this repo's root/docs(eitherdocs/<path>.mdordocs/<path>/index.md) - setting it before the docs page exists fails that test.- New i18n keys get real strings in
aegis/i18n/locales/en.pyand the same keys with English-placeholder values in every other locale in the same change (required fortests/core/test_i18n.pyparity); real translation is left to a separate translator-reviewed pass. - A component that owns tables (like scheduler's Postgres-only execution
history) generates its migration only when the chosen backend needs
persistence - model the
scheduler_backend != "memory"gating inget_services_needing_migrationsand theneeds_migrationsset inpost_gen_tasks.cleanup_components; forgetting either leavesalembic/either missing when needed or present with no matching migration.
Other versions8 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 8
- signerlabs/ShipSwift★ 3.2ksame name
- jpicklyk/task-orchestrator★ 212same name
- ahpxex/open-dashboard★ 136same name
- Azure-Samples/art-voice-agent-accelerator★ 74same name
- DailybotHQ/deepworkplan-website★ 15same name
- dperezcabrera/pico-skills★ 4same name
- IntelliHelper/agent-skills★ 1same name
- nerymurillohnd/claude-essentials★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository14 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- docs/CLAUDE.mdCLAUDE.md
- tests/CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- llms.txtllms.txt
- add-cli-commandSkill
- add-serviceSkill
- create-pluginSkill
- example-skillSkill
- execute-issueSkill
- i18nSkill
- releaseSkill
- squash-branchSkill
- template-devSkill
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.

