---
sidebar_position: 1
question: How do I create a module and what goes in it?
kind: guide

---

> **You are reading the v2.x docs (engine modules). New projects use v3.** [Read this page in v3](/capabilities/build/modules/creating-a-module) · [Migrate from 2.x to 3.0](/capabilities/upgrade/migrate-to-3)

# Creating a Module

Modules are the primary extension unit of ABI. A module packages a set of related agents, integrations, pipelines, workflows, ontologies and apps into a self-contained unit that declares what it depends on. It runs inside the engine's process: the engine loads it from the `modules:` list in `config.yaml`, and the module reaches the engine's services through `naas_abi_core`.

---

## Create the module

The command depends on the ABI line the project runs:

```bash
# ABI 2.x: from the project's src/ folder
cd src
abi new module my_module

# ABI 3.x: from the project root
abi new module my_module --in-engine
```

On 2.x, `abi new module <name> [path]` writes `<path>/<name>/`, the current folder by default: run it from `src/` so the package sits next to the project's own module. On 3.x, `--in-engine` writes `src/<name>/` inside a project. In a 3.x project whose config has no `nats:` block, `abi new module <name>` without the flag also creates this kind of module, and says so.

Both lines end by printing the `modules:` entry to add to `config.yaml` (see [Enabling a module](#enabling-a-module)).

---

## Where modules live

| Where | Path | Use for |
|---|---|---|
| Your project | `src/<name>/` | Your own modules |
| Marketplace | `naas_abi_marketplace.<ai\|applications\|domains>.<name>`, installed as a package extra | Ready-made providers, integrations and domain agents. See the [Marketplace](/v2/capabilities/build/modules/marketplace/marketplace-overview) |
| Framework | `naas_abi` (Nexus and the core agents), `naas_abi_core.modules.*` | Shipped with ABI |

---

## Module structure

`abi new module` scaffolds:

```bash
src/my_module/
├── AGENTS.md               # guidance for coding agents working in the module
├── __init__.py             # ABIModule(BaseModule)
├── agents/                 # <Name>Agent.py
├── integrations/           # <Name>Integration.py: external API wrapper
├── pipelines/              # <Name>Pipeline.py: data into RDF
├── workflows/              # <Name>Workflow.py: business logic as tools and API
├── ontologies/             # modules/, processes/, queries/ (OWL/Turtle)
├── apps/                   # a Nexus app (manifest.json, index.html)
└── orchestrations/         # Dagster (legacy)
```

Only `__init__.py` is required; keep the folders you use. Each scaffold also holds a starter file (a sample agent, integration, pipeline, workflow and ontology).

The `orchestrations/` folder depends on the line: ABI 2.x always writes it. ABI 3.x writes it only in a project without NATS; a NATS project schedules work with [module jobs](/v2/capabilities/build/modules/module-jobs) instead.

---

## Module class

The module's `__init__.py` defines `ABIModule`. The scaffold writes this class with its service imports commented out; here it declares the triple store:

```python
from fastapi import FastAPI
from naas_abi_core.module.Module import (
    BaseModule,
    ModuleConfiguration,
    ModuleDependencies,
)
from naas_abi_core.services.triple_store.TripleStoreService import TripleStoreService


class ABIModule(BaseModule):
    dependencies: ModuleDependencies = ModuleDependencies(
        modules=[],                     # modules this one needs (loaded first)
        services=[TripleStoreService],  # services it uses
    )

    class Configuration(ModuleConfiguration):
        # Module-specific, typed config fields
        pass

    def on_load(self):
        # Called as the module is discovered. Do not touch services or other modules yet.
        super().on_load()

    def on_initialized(self):
        # Called after every module and service is loaded: safe to use them here.
        super().on_initialized()

    def api(self, app: FastAPI) -> None:
        # Optional: mount your own FastAPI routes.
        pass
```

Keep the `super().on_load()` and `super().on_initialized()` calls: the first discovers your agents, workflows, pipelines and ontologies, the second instantiates workflows, pipelines and tools.

The engine computes a load order from every module's `dependencies`. Circular dependencies fail fast.

---

## Enabling a module

Add it to `config.yaml`. In a project the module is a package under `src/`, so its name is the folder name:

```yaml
modules:
  - module: my_module
    enabled: true
    config:
      my_custom_field: "value"
```

`config:` is validated against your `Configuration` class: unknown keys are rejected. Secrets are written `{{ secret.MY_KEY }}` and resolved from the secret service (`.env` by default).

---

## Auto-discovery

When the engine loads your module, it discovers:

- **Ontologies**: every `*.ttl` file under `ontologies/` (recursively; a path containing `sandbox` is skipped). They are loaded into the triple store.
- **Agents**: every agent class defined in `agents/*.py`. The engine calls its `New()`, or the file's module-level `create_agent()` when it defines one.
- **Workflows, pipelines and tools**: classes under `workflows/`, `pipelines/` and `tools/` (recursively). The engine instantiates each with `New()` or with an empty `Configuration`; a class whose constructor needs arguments the engine cannot supply is skipped and logged.
- **Models**: `models/*.py`, registered in the model registry (provider modules).
- **Apps**: `apps/<name>/manifest.json`, listed in Nexus.
- **Orchestrations** (legacy): classes under `orchestrations/`, when Dagster is installed. It comes with `naas-abi-core` on ABI 2.x; on 3.x it is the `naas-abi-core[dagster]` extra.

Every loaded agent gets REST endpoints, and workflows and pipelines with a typed `run(parameters)` get a `POST` route: see the [REST API](/v2/capabilities/build/applications/api).

---

## Accessing services from within a module

Inside a module, `self.engine` is an `EngineProxy` that exposes only the services and modules you declared in `dependencies`:

```python
def on_initialized(self):
    super().on_initialized()
    triple_store = self.engine.services.triple_store
    triple_store.query("SELECT * WHERE { ?s ?p ?o } LIMIT 10")
```

Accessing an undeclared service raises a runtime error: this enforces explicit dependency contracts. (The model registry is the exception: every module can use it.) On ABI 3.x in NATS mode, the services you get are facades that call the kernel services over NATS, so the same code works if a service later runs in its own process.

Store files through the `object_storage` service rather than writing to the local disk; marketplace modules take a `datastore_path` setting for the prefix they use.

---

## Soft dependencies

Suffix a module name with `#soft` to declare an optional dependency:

```python
dependencies = ModuleDependencies(
    modules=["naas_abi_marketplace.ai.anthropic#soft"],
    services=[TripleStoreService],
)
```

The engine loads your module even if the soft dependency is absent.

---

## Scheduled work

How a module schedules work depends on the line and on NATS:

| Project | Scheduled work |
|---|---|
| ABI 3.x with NATS | Module jobs (`@job`), run by JetStream |
| ABI 3.x without NATS | Dagster orchestrations (legacy); a declared job is only logged |
| ABI 2.x | Dagster orchestrations |

### Module jobs (ABI 3.x, NATS)

A module that must run something on a schedule, or when an event happens, declares a **job**. JetStream schedules it and delivers the trigger; there is no central scheduler. Handlers may be synchronous (the engine runs them in a worker thread):

```python
from datetime import timedelta
from naas_abi_core.module.jobs import Cron, JobContext, job

class ABIModule(BaseModule):
    @job(triggers=(Cron("0 0 2 * * *", time_zone="UTC"),), timeout=timedelta(hours=1))
    def nightly_sync(self, ctx: JobContext) -> dict:
        ...
        return {"synced": 42}
```

`Cron` takes six fields, seconds first. Every declared job needs a handler. Runs, retries and logs show in the Nexus System app's **Jobs** tab. How jobs work, step by step: [Module jobs](/v2/capabilities/build/modules/module-jobs).

### Dagster orchestrations (legacy)

Without NATS, scheduled work is a Dagster orchestration: `abi new orchestration`, in the module's `orchestrations/` folder. See [Dagster](/v2/capabilities/build/applications/dagster). Dagster is removed in the next major.

---

## Testing

Test the units of your module directly: a workflow's `run(...)`, a pipeline's graph, an integration with its HTTP layer mocked. They do not need an engine.

To check that the module loads, build an engine:

```python
# tests/test_my_module.py
from naas_abi_core.engine.Engine import Engine

def test_module_loads():
    engine = Engine()
    engine.load(module_names=["my_module"])
    assert "my_module" in engine.modules
```

On ABI 3.x in a NATS project, `Engine()` connects to a broker, so either run one (`abi dev up`, or `abi stack start`) or point `ABI_CONFIG_OVERLAY` at a YAML file holding `nats: null`. See [Test a module](/v2/capabilities/build/modules/test-a-module).

Next steps: [Creating an Agent](/v2/capabilities/build/agents/creating-an-agent), [Creating an Integration](/v2/capabilities/build/agents/creating-an-integration), [Creating a Workflow](/v2/capabilities/build/agents/creating-a-workflow), [Creating a Pipeline](/v2/capabilities/build/knowledge-graph/creating-a-pipeline).
