Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/nemo_platform_plugin/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- **Discovery via entry-points**: Plugins register surfaces under entry-point groups (`nemo.services`, `nemo.cli`, `nemo.jobs`, `nemo.controllers`) in `pyproject.toml`. The platform scans these at startup — no code registration needed.
- **Entities for persistent state**: Use `NemoEntity` + `NemoEntitiesClient` to store plugin data in the NeMo Platform entity store. Entity types are global — use plugin-scoped names (`"my_plugin_widget"` not `"widget"`).
- **Fault isolation**: A broken plugin (import error) logs a warning and is skipped at startup — the platform continues.
- **Route authorization is mandatory**: every plugin HTTP route must carry an authz rule — `@path_rule` on the handler, or `authz=` on the job / function route factory — or the auth service refuses to build the OPA bundle (`on_invalid_plugin=hard_fail`, the default) and the platform 502s. There is no silent fencing; an unruled route fails closed.

## Always load a skill first

Expand All @@ -19,6 +20,7 @@ Before writing any plugin code, load the relevant skill. Skills contain exact im
- **`plugin-config`** → adding `NemoConfig` fields, env var naming, test overrides
- **`plugin-job`** → adding `NemoJob` surfaces, the three-verb CLI (`run` / `submit` / `explain`), `spec_schema` / `input_spec_schema` / `to_spec` / `compile`, mounting routes with `add_job_routes`, container execution
- **`plugin-service`** → adding HTTP routes with `NemoService`, `RouterSpec`, response schemas, pagination
- **`plugin-authz`** → declaring HTTP authorization on plugin routes: `@path_rule`, `AuthzScope` / `PermissionSet`, caller-kind (`PRINCIPAL` vs `SERVICE_PRINCIPAL`), the `hard_fail` bundle build, migrating off `get_authz_contribution`
- **`plugin-controller`** → background reconcile loops with `NemoController`, `on_startup()` patterns, service-principal clients
- **`plugin-platform-services`** → calling platform services (jobs, files, secrets, models, inference gateway, auth) from a plugin
- **`plugin-testing`** → writing tests for any plugin surface — entity client mocking, service route tests, job tests, config overrides
Expand All @@ -38,6 +40,7 @@ Before writing any plugin code, load the relevant skill. Skills contain exact im
- [`plugin-config`](src/nemo_platform_plugin/.agents/skills/plugin-config/SKILL.md) — Creates plugin configuration using `NemoConfig` with environment variables and YAML file support. Use when adding plugin configuration fields, reading config values at runtime, setting up test config overrides, or understanding the env var naming formula. _Trigger keywords: config, configuration, NemoConfig, env var, environment variable, plugin_name, NMP_CONFIG, YAML config, config override, test config._
- [`plugin-job`](src/nemo_platform_plugin/.agents/skills/plugin-job/SKILL.md) — Creates schedulable `NemoJob` surfaces for NeMo Platform plugins. Use when adding a job, declaring `spec_schema` / `input_spec_schema` / `to_spec` / `compile`, mounting job routes with `add_job_routes`, understanding the three CLI verbs (`run` / `submit` / `explain`), or running jobs in containers. _Trigger keywords: job, NemoJob, spec_schema, input_spec_schema, to_spec, compile, add_job_routes, nemo_platform_plugin.jobs, three verbs, run, submit, explain, NemoJobScheduler._
- [`plugin-service`](src/nemo_platform_plugin/.agents/skills/plugin-service/SKILL.md) — Builds HTTP service surfaces for NeMo Platform plugins using `NemoService`, `RouterSpec`, `NemoListResponse`, and `NemoFilter`. Use when adding REST API routes to a plugin, implementing CRUD endpoints, handling pagination and filtering, or testing FastAPI routes. _Trigger keywords: HTTP routes, REST API, FastAPI, CRUD, endpoint, router, NemoService, pagination, filter, list endpoint, NemoListResponse, RouterSpec._
- [`plugin-authz`](src/nemo_platform_plugin/.agents/skills/plugin-authz/SKILL.md) — Declares HTTP authorization on plugin routes with `@path_rule`, `AuthzScope`, and `PermissionSet`. Use when attaching authz rules to route handlers, picking caller kinds (`PRINCIPAL` vs `SERVICE_PRINCIPAL`), passing `authz=` to `add_job_routes` / `add_function_routes`, granting permissions with no 1:1 route via `extra_permissions` / `extra_role_permissions`, or migrating a plugin off the removed `get_authz_contribution`. _Trigger keywords: authz, authorization, path_rule, AuthzScope, PermissionSet, perm, permission, caller kind, PRINCIPAL, SERVICE_PRINCIPAL, OPA bundle, hard_fail, on_invalid_plugin, extra_permissions, extra_role_permissions, get_authz_contribution._
- [`plugin-controller`](src/nemo_platform_plugin/.agents/skills/plugin-controller/SKILL.md) — Creates background reconcile-loop controllers using `NemoController`. Use when implementing state-machine reconciliation, running periodic background work, managing deployment lifecycle, building service-principal entity clients for background use, or understanding controller startup/shutdown sequence. _Trigger keywords: controller, NemoController, reconcile, background loop, reconcile_one, list_objects, on_startup, state machine, deployment lifecycle, service principal, interval_seconds._
- [`plugin-platform-services`](src/nemo_platform_plugin/.agents/skills/plugin-platform-services/SKILL.md) — Calls NeMo Platform services (entity store, jobs, files, secrets, models, inference gateway, auth) from a plugin. Use when a plugin needs to submit jobs, access files, read secrets, look up models, call the inference gateway, check permissions, or route calls between services. _Trigger keywords: jobs service, files service, secrets service, models service, inference gateway, auth client, NeMo SDK, platform SDK, service-to-service, inter-service call, job_route_factory, NMP_BASE_URL._
- [`plugin-testing`](src/nemo_platform_plugin/.agents/skills/plugin-testing/SKILL.md) — Tests NeMo Platform plugin surfaces without a running platform. Use when writing tests for entity CRUD routes, mocking the entity client, testing `NemoJob.run()` methods, setting up config overrides, or verifying FastAPI route error handling. _Trigger keywords: test, pytest, mock entity client, TestClient, dependency_overrides, AsyncMock, test job, test config, test service, test controller._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ packages = ["src/nemo_my_plugin"]
# src/nemo_my_plugin/service.py
from typing import ClassVar
from fastapi import APIRouter
from nemo_platform_plugin.authz import AuthzScope, CallerKind, path_rule
from nemo_platform_plugin.service import NemoService, RouterSpec

scope = AuthzScope("my-plugin")

class MyService(NemoService):
name: ClassVar[str] = "my-plugin"
dependencies: ClassVar[list[str]] = []
Expand All @@ -52,12 +55,16 @@ class MyService(NemoService):
router = APIRouter()

@router.get("/hello")
@scope.read
@path_rule(callers=[CallerKind.PRINCIPAL], permissions=[])
async def hello() -> dict:
return {"message": "Hello from my plugin!"}

return [RouterSpec(router, tag="My Plugin")]
```

Every plugin route needs an authz rule — the `@scope.read`/`.write` scope gate plus a `@path_rule` (here authenticated but permissionless, `permissions=[]`); see the `plugin-authz` skill for permissions and caller-kinds.

**Step 4: Install editable**

```bash
Expand Down Expand Up @@ -166,7 +173,7 @@ class SayHelloJob(NemoJob):
...
```

Entry-point key uses dot: `"my-plugin.say-hello"` under the `nemo.jobs` group. The platform auto-generates `nemo my-plugin say-hello run / submit / explain`. Mount server routes with `add_job_routes(SayHelloJob)` from `nemo_platform_plugin.jobs.routes`. See the `plugin-job` skill for the full pattern.
Entry-point key uses dot: `"my-plugin.say-hello"` under the `nemo.jobs` group. The platform auto-generates `nemo my-plugin say-hello run / submit / explain`. Mount server routes with `add_job_routes(SayHelloJob, authz=AuthzScope("my-plugin"))` from `nemo_platform_plugin.jobs.routes` — the `authz=` kwarg is required, or the generated routes are unruled and fail the OPA bundle build. See the `plugin-job` skill for the full pattern.

**Add a function:**

Expand Down Expand Up @@ -194,7 +201,7 @@ class GreetFunction(NemoFunction[GreetSpec]):
return GreetResponse(message=f"Hello, {spec.name}!")
```

Entry-point key uses dot: `"my-plugin.greet"` under the `nemo.functions` group. The platform auto-generates `nemo my-plugin greet run / submit` (two verbs — no `explain`). Mount the HTTP route inside your `NemoService` with `add_function_routes(GreetFunction)` from `nemo_platform_plugin.functions.routes`. Streaming functions return an `AsyncIterator` (one NDJSON frame per line); non-streaming ones return a value. `run` **must be `async def`** — sync work goes through `await asyncio.to_thread(...)`. See the `plugin-function` skill for the full pattern.
Entry-point key uses dot: `"my-plugin.greet"` under the `nemo.functions` group. The platform auto-generates `nemo my-plugin greet run / submit` (two verbs — no `explain`). Mount the HTTP route inside your `NemoService` with `add_function_routes(GreetFunction, authz=AuthzScope("my-plugin"), permission_description="Invoke the greet function")` from `nemo_platform_plugin.functions.routes` — the `authz=` kwarg is required, or the route is unruled and fails the OPA bundle build. Streaming functions return an `AsyncIterator` (one NDJSON frame per line); non-streaming ones return a value. `run` **must be `async def`** — sync work goes through `await asyncio.to_thread(...)`. See the `plugin-function` skill for the full pattern.

**Add a controller:**

Expand Down Expand Up @@ -273,3 +280,4 @@ discover_entry_points.cache_clear()
- **Install with `-e` (editable)**: `uv pip install -e .` — non-editable installs require reinstall on every change.
- **`discover.cache_clear()` in tests**: Any test that mocks entry-points must call both `discover.cache_clear()` and `discover_entry_points.cache_clear()` to avoid stale caches between tests.
- **`packages = ["src/nemo_my_plugin"]` in hatchling config**: Without this, the wheel will not include the `nmp` namespace package correctly.
- **Every route needs an authz rule**: an unruled route fails the OPA bundle build under `on_invalid_plugin=hard_fail` (the platform 502s rather than silently fencing the route). Attach `@scope.read`/`.write` + `@path_rule` to hand-written handlers, or pass `authz=` to `add_job_routes` / `add_function_routes`. See the `plugin-authz` skill.
Loading