For the complete documentation index, see llms.txt. This page is also available as Markdown.

Create and Publish a Perceptor

This guide walks you through everything you need to build a Perceptor from scratch and publish it to Amesa Orchestration Studio using the amesa CLI. By the end, you will have a working perceptor packaged as an artifact and registered in the AMESA registry.

What Is a Perceptor?

A Perceptor transforms raw sensor observations into derived, computed features. It sits between the sensor layer and your agents: it receives the raw sensor dictionary, computes new values from it, and injects those new keys into the observation namespace before any agent sees the data.

Agents can reference perceptor outputs the same way they reference sensor names — by listing the key in filtered_sensor_space(). Perceptors are registered on the Orchestration and run for every agent.

Common use cases:

  • Rate-of-change / derivative of a sensor value

  • Running averages and sliding-window statistics

  • Threshold flags (is this sensor in spec?)

  • Composite metrics derived from two or more sensors

Data Flow

Simulator
    │ raw observations

Sensor mapping  →  { "temperature": 82.3, "pressure": 1.1, ... }


Perceptor pipeline  (registered order; each adds new keys)
    │  perceptor 1 adds { "efficiency_ratio": 0.91 }
    │  perceptor 2 adds { "quality_index": 0.87 }

Agent teachers and controllers  (can reference all keys, including perceptor outputs)
1

Understand the Perceptor Methods

A Perceptor is a subclass of PerceptorImpl from amesa_core. You must implement exactly two methods.

Required Methods

Method
Sync/Async
What it does

compute(obs_spec, obs)

async

Called every step. Returns a dict of new keys to inject into the observation namespace.

filtered_sensor_space(obs)

sync

Returns the list of raw sensor names this perceptor reads. Used for shape inference at initialization.

filtered_sensor_space must be sync. Do not declare it async. Declaring it async will break space construction at training initialization.

compute must be async. Declare it with async def.

Method Signatures

The compute() Parameters

Parameter
Type
Description

obs_spec

Space | None

Gymnasium Space spec. May be None in some call paths — do not depend on it being a valid Space object.

obs

dict

The full named sensor dict after lambda extraction, plus outputs from any perceptors that ran before this one.

Return value: a dict of new { key: value } pairs. Every key must:

  1. Not already exist in the observation dict — a collision with an existing sensor name raises an error at training initialization

  2. Match the variables list declared in pyproject.toml

Episode state: __init__ is called fresh at the start of each episode. All self.* state is wiped at every episode reset. Do not rely on state persisting across episodes.

2

Write Your Perceptor

The example below computes two derived metrics from raw process sensors: an efficiency_ratio (the running fraction of steps where throughput meets a threshold) and a quality_index (the ratio of output rate to input rate).

Common compute() Patterns

Step-over-step delta (derivative):

Running average:

Threshold flag:

3

Create the Artifact Directory

This creates:

Replace the placeholder perceptor.py with your implementation from Step 2.

Creating Manually

Naming Rules

Layer
Convention
Example

Outer directory

kebab-case

process-monitor

Inner Python module

snake_case

process_monitor

Source file

by convention

perceptor.py

__init__.py

always empty

(no content)

The outer directory is used by the CLI only. The inner snake_case directory is the Python module referenced in pyproject.toml. Do not use the kebab-case outer name in your entrypoint — it is not a valid Python identifier.

4

Configure pyproject.toml

Field Reference

Field
Where
Required
Description

name

[project]

Yes

Artifact name as it will appear in the registry. Kebab-case.

version

[project]

Yes

Semantic version string.

description

[project]

Yes

Short human-readable description.

dependencies

[project]

Yes

Must include "amesa-core".

type

[amesa]

Yes

Must be "perceptor".

entrypoint

[amesa]

Yes

inner_module.filename:ClassName

variables

[amesa]

Yes — perceptors only

List of output key names. Must exactly match the keys returned by compute().

variables is required for perceptors. This is what the AMESA registry uses to know what named outputs the perceptor exposes. Missing or mismatched variables causes publish validation to fail or training initialization to fail.

Entrypoint Format

Part
Value in this example

inner_module

process_monitor (inner snake_case directory)

filename

perceptor (the .py file, without extension)

ClassName

ProcessMonitorPerceptor

Full entrypoint: process_monitor.perceptor:ProcessMonitorPerceptor

5

Verify Your Directory

Checklist:

6

Publish

Or using the flag form:

The path must point to the outer kebab-case directory containing pyproject.toml.

7

Confirm the Publish

This prints a table of all perceptors in the selected project:

Name
Version
Description
UUID

process-monitor

1

Computes efficiency_ratio and quality_index...

...

Attaching a Perceptor to an Orchestration

Once published, a perceptor can also be used in-process (before packaging) while developing:

Perceptors run in registration order. Each receives the full observation dict including outputs from perceptors that ran before it.

Updating a Published Perceptor

Increment the version in pyproject.toml and republish:

Deleting a Perceptor

The CLI presents an interactive list. Select the perceptor to remove and confirm.

Troubleshooting

Output keys don't match variables in pyproject.toml

The variables list in [amesa] must exactly match the dict keys returned by compute(). A mismatch causes training initialization to fail when the orchestration tries to resolve perceptor outputs by name.

filtered_sensor_space is async — training fails at init

filtered_sensor_space must be a regular synchronous method. Making it async breaks the space construction step that runs before training starts.

Key collision with existing sensor name

If compute() returns a key that already exists in the sensor dict, AMESA raises an error at training initialization. Choose output key names that are distinct from all sensor names registered on the Orchestration.

Legacy import error