Authoring a module¶
Condensed walkthrough of adding a new module to Jarvis. Every command below
is real and copy-pasteable, taken from the modules/sample/ module already
in the repo.
The full guide
This page is a condensed version. For the complete, field-by-field
walkthrough — including the CoreContext seam, the contributes.*
hook, and recipes for adding a tab / an agent / a collection — read the
full guide in the repo:
docs/authoring-a-module.md.
What a module is¶
Jarvis has one hard invariant: adding a module never modifies the core.
A module is a self-contained unit — its own manifest, its own database
schema, its own optional background agent, its own optional Flutter tab —
that plugs into the platform without anyone hand-editing core/, app/, or
backend/ source.
That's enforced structurally: core/ never imports anything from
modules/; only the thin app/ entrypoint depends on both core/ and
whichever modules are enabled (listed in the root modules.yaml, not in
code). Enabling a module runs a code generator instead of any hand-wiring —
see Architecture for what it regenerates.
The contract¶
A module is a directory under modules/<name>/ with one required file and
up to three optional pieces:
modules/<name>/
├── module.yaml # manifest — the only required file
├── schema/
│ └── pb_migrations/ # PocketBase JS migrations, prefix-namespaced by module
├── agent/ # OPTIONAL — a Python job on the agent-runtime framework
│ └── agent.py
└── lib/ # OPTIONAL — a Flutter package: the tab(s) this module adds
└── <name>_module.dart
module.yaml declares the module's slug, title, identity tag
(personal/freelance/rotary/shared), its Flutter surface (package,
entry file, tabs variable), and an optional agent: entrypoint. It can also
declare contributes.{obligations,entities}: true to say the module feeds
one of the core's shared spine collections — a purely declarative hook that
flows straight into the generated backend manifest.
Scaffold → enable → deploy¶
Run this from the repo root, through the ./jarvis wrapper (unrecognized
verbs like module and deploy forward straight into the containerized
jarvis CLI):
./jarvis module new sample --identity freelance
./jarvis module add sample
./jarvis deploy
jarvis module new <name> scaffolds the whole modules/<name>/ tree above
— manifest, pubspec, surface tab, seed migration, agent stub, a Flutter
test, analysis_options.yaml, .gitignore — filled in from <name> and
--identity (default personal). It fails loudly if the directory already
exists.
jarvis module add <name> does two things: it appends <name> to the
enabled: list in the root modules.yaml, then regenerates four generated
artifacts from the full set of enabled modules' manifests:
app/lib/generated/enabled_modules.dart— the Flutter tab registry.backend/generated/manifest.json— the module manifest the backend andjarvis deployread.backend/generated/crontab— the scheduler's crontab, built from every enabled module'sschedules:.- The managed block in
app/pubspec.yaml— a local path dependency on each surfaced module's package.
You never hand-edit these four — they're build output, rewritten wholesale
on every module add.
jarvis deploy is a P0 skeleton: it reads backend/generated/manifest.json,
stages the baked migration list (core migrations first, then each enabled
module's migrations), and echoes what a real deploy would do — a multi-arch
image build/push and a cron install per module that declares an agent. It
executes no Docker.
After module add sample, sample renders as a freelance-tagged tab in
the running app — and none of core/, app/ source, or any other module's
files changed.
Before you PR¶
This page covers the module side only. Before opening a PR, run the full monorepo gate and follow Conventional Commits and the one-slice-per-PR convention — see Dev setup & the gate for the exact commands and counts.