Aja — Motion AI and Animation Platform
This document specifies the implemented surface of the Aja domain: the domain
objects and their fields, enums and union types, state values, HTTP API surface,
in-process event model, persistence, validation rules, configuration, and
acceptance criteria. Every type, field, enum value, endpoint, and event named
here is traceable to source under libs/aja/* or apps/aja/*.
A note on naming: the motion-pipeline service, its SDK, and several libraries
were authored under the @lilith/ / "Lilith Motion Pipeline" name and retain
those internal labels in code comments and one package name
(@lilith/svc-reference-video). The domain itself is Aja. Where the code uses
@lilith/..., this document notes it.
1. Domain Inventory#
Aja consists of 39 library projects under libs/aja/ and 5 application
projects under apps/aja/.
Applications (apps/aja/)#
The table below lists each application, its published package name, runtime framework, default port, and primary responsibility.
| Project | Package | Runtime | Default port | Responsibility |
|---|---|---|---|---|
svc-motion-pipeline |
@aja/svc-motion-pipeline |
Fastify | 8090 |
Pipeline orchestration: configs, jobs, delivery |
svc-motion-ai |
@aja/svc-motion-ai |
Hono | 3040 |
Video-to-motion, retargeting, skeletal animation, embodied instruction |
svc-reference-video |
@lilith/svc-reference-video |
library | — | Reference video library: ingest, scene/metadata, search, annotation, collaboration |
cli |
@aja/cli |
Commander | — | Command-line interface (aja / aja-motion) |
docs |
(unnamed) | — | — | Markdown documentation site (README.md, api/, examples/, integration/, best-practices/) |
svc-reference-video ships as a TypeScript library (main: dist/index.js, no
HTTP server entry) — its module exports the reference-video subsystem.
Libraries (libs/aja/)#
All libraries publish under the @aja/ scope except the Python SDK. The 39
libraries listed below are alphabetically ordered for lookup.
animation-blending, asset-storage, avatar-integration, avatar-library,
avatar-preview-ui, batch-inference, blend-shape-retargeting,
bone-mapping-ui, consent-management, content-moderation,
content-security, content-watermarking, data-retention, depth-sensing,
distributed-workers, domain-motion-pipelines, film-pipeline,
fitness-animation, human-mesh-recovery, model-optimization,
motion-formats, motion-integration, motion-pipeline-sdk,
motion-pipeline-sdk-python, motion-processing, motion-quality,
motion-validation, multi-view-reconstruction, neural-retargeting,
optimization-ik, pipeline-cache, pipeline-parallelism, pose-lifting,
privacy-protection, proportional-adaptation, result-aggregation,
semantic-retargeting, skeleton-mapping, video-chunking.
motion-pipeline-sdk-python has no package.json name; its Python
distribution (pyproject.toml) is aja-motion-pipeline and its import package
is aja_motion_pipeline.
2. Core Motion Data Models (@aja/motion-formats)#
@aja/motion-formats is the foundation library: it defines the canonical motion
data model used across all 39 Aja libraries and the per-format file structures
for import and export. No other Aja library defines its own motion primitives —
they all re-export or depend on this one. Source:
libs/aja/motion-formats/src/types.ts.
2.1 Math and Color Primitives#
The math primitives below are used everywhere joint positions and rotations are stored. All rotation representations are provided so callers can work in whatever convention their rendering pipeline expects.
| Type | Shape |
|---|---|
Point3D |
{ x, y, z: number } |
Point2D |
{ x, y: number } |
Quaternion |
{ x, y, z, w: number } |
EulerAngles |
{ x, y, z: number; order: EulerOrder } |
AxisAngle |
{ axis: Point3D; angle: number } |
Matrix4x4 |
16-number tuple, row-major |
Matrix3x3 |
9-number tuple, row-major |
Color3 |
{ r, g, b: number } |
Color4 |
{ r, g, b, a: number } |
EulerOrder is one of 'XYZ' | 'XZY' | 'YXZ' | 'YZX' | 'ZXY' | 'ZYX'.
2.2 SkeletonJoint and SkeletonDefinition#
A skeleton is a tree of joints. SkeletonJoint represents one node in that tree
— its position, orientation, scale, and its connection to its parent and
children via index references.
SkeletonJoint fields:
| Field | Type | Notes |
|---|---|---|
name |
string |
required |
index |
number |
array index |
parentIndex |
number |
-1 for root |
localPosition |
Point3D |
required |
localRotation |
Quaternion |
required |
localScale |
Point3D |
required |
worldPosition |
Point3D |
optional |
worldRotation |
Quaternion |
optional |
children |
number[] |
child joint indices |
SkeletonDefinition wraps the joint array with top-level metadata: name,
joints: SkeletonJoint[], rootIndex, optional restPose: AnimationFrame,
optional metadata.
2.3 Animation Model#
The animation model builds from individual keyframe values up to a full clip. Understanding this hierarchy is important before reading the type definitions.
KeyframeInterpolation describes how values are interpolated between keyframes:
'linear' | 'step' | 'cubic' | 'hermite' | 'bezier'.
AnimationProperty identifies which aspect of a joint a curve animates — one of
position.{x,y,z}, rotation.{x,y,z,w}, euler.{x,y,z}, scale.{x,y,z},
blendShape.
Keyframe<T> is a single timed sample: time, value: T,
interpolation: KeyframeInterpolation, optional inTangent/outTangent.
AnimationCurve<T> is the full set of keyframes for one property on one joint:
jointName, property: AnimationProperty, keyframes: Keyframe<T>[].
JointTransform captures the transform state of a single joint at one moment:
jointIndex, jointName, localPosition, localRotation, localScale,
optional worldPosition/worldRotation.
AnimationFrame is the full skeleton state at one point in time: time,
frameIndex, jointTransforms: JointTransform[], optional rootPosition,
rootRotation, blendShapes: Map<string, number>.
AnimationClip is the central motion object — a complete recorded or generated
animation sequence. Its fields are:
| Field | Type | Required |
|---|---|---|
id |
string |
yes |
name |
string |
yes |
skeleton |
SkeletonDefinition |
yes |
frames |
AnimationFrame[] |
yes |
duration |
number (seconds) |
yes |
frameRate |
number |
yes |
frameCount |
number |
yes |
loopable |
boolean |
yes |
curves |
AnimationCurve<number>[] |
optional |
events |
AnimationEvent[] |
optional |
metadata |
AnimationMetadata |
optional |
AnimationEvent marks a named moment in the timeline: time, name, optional
data.
AnimationMetadata carries provenance and categorization for the clip:
source, optional captureDate, performer, action,
category: MotionCategory, tags, quality: QualityMetrics, customData.
MotionCategory classifies the type of motion:
'locomotion' | 'combat' | 'dance' | 'sports' | 'gesture' | 'expression' | 'interaction' | 'idle' | 'transition' | 'other'.
QualityMetrics carries the six scoring fields inline with the clip:
jitterScore (0–1, lower better), footSlidingDistance (meters),
boneLengthVariance (0–1, lower better), smoothnessScore (0–1, higher
better), physicalPlausibility (0–1, higher better), overallScore (0–1,
higher better).
2.4 Standard Skeleton Types#
StandardSkeletonType is the union of all skeleton conventions that Aja knows
about out of the box:
'mixamo' | 'unity-humanoid' | 'unreal-mannequin' | 'maya-hik' | 'motionbuilder' | 'cmu' | 'h36m' | 'coco' | 'openpose' | 'mediapipe-pose' | 'smpl' | 'smplx' | 'custom'.
Two exported constants drive all cross-skeleton work and must stay index-aligned
— that is, index i in STANDARD_JOINT_NAMES[type] must correspond to index
i in SKELETON_PARENT_INDICES[type]:
STANDARD_JOINT_NAMES: Record<StandardSkeletonType, string[]>— the canonical joint name list per skeleton (e.g., 65 names formixamo, 22 forunity-humanoid, 22 forunreal-mannequin, 17 forh36m, 17 forcoco, 34 formediapipe-pose, 24 forsmpl, 55 forsmplx).SKELETON_PARENT_INDICES: Record<StandardSkeletonType, number[]>— parent joint index per joint for the same skeletons.
2.5 Supported Motion Formats#
MotionFormat is the union of all format identifiers:
'bvh' | 'fbx' | 'gltf' | 'glb' | 'usd' | 'usda' | 'usdc' | 'usdz' | 'alembic' | 'abc' | 'c3d' | 'trc' | 'asf' | 'amc' | 'json' | 'internal'.
Each format has a corresponding file-structure interface for typed I/O. The per-format structures are:
- BVH —
BVHFile(hierarchy: BVHHierarchy,motion: BVHMotion),BVHJoint,BVHChannel(Xposition…Zrotation), withBVHImportOptions/BVHExportOptionsandDEFAULT_BVH_*_OPTIONS. - FBX —
FBXNode,FBXProperty,FBXTransform,FBXAnimationStack,FBXAnimationLayer,FBXAnimationCurve,FBXSkeleton;FBXVersion('7100' … '7700');FBXNodeType; import/export options + defaults. - glTF/GLB —
GLTFFileand the full glTF 2.0 object graph (GLTFNode,GLTFSkin,GLTFAnimation,GLTFAccessor,GLTFBufferView, …); GLB chunk magic constantsGLB_CHUNK_JSON/GLB_CHUNK_BIN; import/export options. - USD —
USDFile,USDPrim,USDAttribute,USDSkeleton,USDSkelAnimation,USDBlendShape;USDSchemaType;USDExportOptions.formatis'usda' | 'usdc' | 'usdz'; import/export options. - Alembic —
AlembicFile,AlembicObject,AlembicProperty,AlembicXformSample,AlembicPolyMeshSample,AlembicSkeletonData;AlembicExportOptions.backendis'Ogawa' | 'HDF5'. - C3D —
C3DFile,C3DMarker,C3DAnalogChannel,C3DForcePlate,C3DEvent,C3DHeader;DEFAULT_C3D_IMPORT_OPTIONS(scaleFactor: 0.001, mm→m). - TRC —
TRCFile,TRCHeader,TRCMarker;DEFAULT_TRC_IMPORT_OPTIONS. - ASF/AMC —
ASFFile,ASFBone,ASFHierarchy,AMCFile,AMCFrame;DEFAULT_ASF_AMC_IMPORT_OPTIONS.
The library's public entry point (index.ts) exports the functions
importAnimation, exportAnimation, convertAnimation, and
detectFormat(filenameOrContent) (returns MotionFormat | null). Three type
maps associate each format with its structures and options: FormatFileTypeMap,
FormatExportOptionsMap, and FormatImportOptionsMap. Note that
FormatExportOptionsMap defines export options only for bvh, fbx,
gltf/glb, usd*, and alembic/abc — C3D, TRC, and ASF/AMC have import
options but no export options entry.
2.6 Motion Clip Database#
To make motion clips discoverable and reusable, the library provides a typed database record and a rich query model.
MotionClipEntry — a database record for a motion clip: id, name, optional
description, category: MotionCategory, tags, duration, frameRate,
frameCount, skeletonType: StandardSkeletonType, jointCount,
sourceFormat: MotionFormat, optional filePath/fileSize,
quality: QualityMetrics, metadata: AnimationMetadata, createdAt,
updatedAt, version, optional parentId, variants.
MotionClipQuery supports filtering by name, category, tags
(tagsMatchAll), duration range, skeletonType, minQuality, performer,
action, date range, with limit/offset and sortBy
('name' | 'duration' | 'quality' | 'createdAt' | 'updatedAt') / sortOrder.
MotionClipSearchResult returns entries, total, limit, offset.
MotionClipCollection groups clip IDs.
3. Skeleton Models (@aja/skeleton-mapping)#
@aja/skeleton-mapping extends the core skeleton model with richer typing for
retargeting operations. It re-exports the math and skeleton primitives from
@aja/motion-formats and adds its own broader skeleton modeling needed for
automatic bone matching, partial skeletons, and the normalized primal skeleton.
Source: libs/aja/skeleton-mapping/src/types.ts.
3.1 Skeleton Templates#
StandardSkeletonType in this library is a wider union than in
@aja/motion-formats, covering more conventions: animation-software standards
(mixamo, unity-humanoid, unreal-mannequin, maya-hik, motionbuilder,
blender-rigify), mocap formats (cmu, h36m, coco, openpose-body25,
openpose-body18, mediapipe-pose, mediapipe-holistic), body models (smpl,
smplx, star), fitness/sports skeletons (fitness-standard, yoga-enhanced,
dance-extended), and custom.
SkeletonTemplate is the full definition used for matching and retargeting:
id, name, type, description, joints: JointDefinition[],
bones: BoneConnection[], rootJointId,
defaultProportions: SkeletonProportions, regions: BodyRegion[],
hasFingers, hasFacial, referenceHeight (meters), optional metadata.
JointDefinition provides the per-joint detail needed for automatic matching:
id, name, aliases: string[], parentId (string | null),
defaultPosition, defaultRotation (quaternion), region: BodyRegion,
jointType: JointType, optional rotationLimits: RotationLimits,
isEndEffector, index.
BoneConnection describes the relationship between two joints: parentJointId,
childJointId, lengthRatio (relative to skeleton height), optional name.
BodyRegion classifies which part of the body a joint belongs to:
'root' | 'spine' | 'head' | 'left_arm' | 'right_arm' | 'left_hand' | 'right_hand' | 'left_leg' | 'right_leg' | 'left_foot' | 'right_foot' | 'face'.
JointType is a 50+-value union covering the full human anatomy. The values
span core joints (root, pelvis, spine, chest, upper_chest, neck,
head), arm joints, the complete set of finger joints (thumb_base/mid/tip,
index_*, middle_*, ring_*, pinky_*), leg/foot joints, face joints
(jaw, eye, eyebrow, nose, mouth, ear), and generic types (twist,
auxiliary, unknown).
RotationLimits specifies anatomical range-of-motion constraints per axis:
per-axis { min, max } for x, y, z (degrees).
SkeletonProportions captures 14 named body ratio fields — headRatio,
neckRatio, torsoRatio, armRatio, forearmRatio, upperArmRatio,
legRatio, thighRatio, shinRatio, shoulderWidthRatio, hipWidthRatio,
armSpanRatio, handRatio, footRatio — used for proportional adaptation
during retargeting. DEFAULT_PROPORTIONS provides average-adult values.
3.2 Skeleton Mapping#
A SkeletonMapping records how joints from one skeleton correspond to joints of
another, with enough detail for the retargeter to handle merged joints, split
joints, and procedurally synthesized joints.
SkeletonMapping fields: id, sourceTemplateId, targetTemplateId,
sourceSkeletonType, targetSkeletonType, jointMappings: JointMapping[],
confidence, scaleFactor, createdBy ('manual' | 'automatic' | 'hybrid'),
createdAt, optional metadata.
JointMapping describes one source-to-target joint connection: sourceJointId,
targetJointId, confidence (0–1), mappingType: JointMappingType, optional
weight, rotationOffset, positionOffset, localScale,
additionalSources/additionalTargets, blendWeights.
JointMappingType specifies the relationship kind:
'direct' | 'merged' | 'split' | 'interpolated' | 'procedural' | 'ignored' | 'synthesized'.
3.3 Automatic Matching#
The automatic matcher combines four complementary strategies to handle skeleton
variations it has never seen before. AutoMatchConfig toggles each strategy
independently (useNameMatching, useHierarchyMatching,
usePositionalMatching, useSemanticMatching) and sets minConfidence, plus
fine-grained options for each strategy:
NameMatchOptions: fuzzy matching, threshold, name normalization, alias use, custom mappings.HierarchyMatchOptions: match by depth/sibling/child count,depthTolerance.PositionalMatchOptions: position normalization, distance threshold, proportional matching.
DEFAULT_AUTO_MATCH_CONFIG enables all four strategies with
minConfidence: 0.5 and fuzzyThreshold: 0.8.
AutoMatchResult carries the full outcome of a matching run: mapping,
statistics: MatchStatistics, matchDetails: JointMatchDetail[],
unmatchedSourceJoints, unmatchedTargetJoints, warnings. MatchStatistics
records how many joints were matched by each of the four strategies.
JointMatchDetail records per-joint matchMethod
('name' | 'hierarchy' | 'position' | 'semantic' | 'manual' | 'none') and the
four strategy sub-scores.
3.4 Partial and Primal Skeletons#
Partial skeletons allow retargeting only a subset of the body — for example, applying captured upper-body motion to a procedurally animated lower body.
PartialSkeletonConfig specifies the subset: includedRegions: BodyRegion[],
includeIKTargets, boundaryHandling ('include' | 'exclude' | 'blend'),
preserveHierarchy, optional customRootJoint. PartialSkeleton carries the
extracted joints, bones, root, and boundaryJoints: BoundaryJoint[].
PartialSkeletonPreset names the common presets:
'upper_body' | 'lower_body' | 'torso_only' | 'arms_only' | 'left_arm_only' | 'right_arm_only' | 'legs_only' | 'left_leg_only' | 'right_leg_only' | 'hands_only' | 'face_only' | 'head_and_hands' | 'full_body'.
The primal skeleton is a topology-agnostic 18-joint humanoid skeleton used as a
normalization intermediate. PrimalSkeleton represents it; PrimalJointId
enumerates its 18 joints (root, pelvis, spine, chest, neck, head,
and left/right shoulder/elbow/wrist and hip/knee/ankle). PrimalMapping maps
any template to and from this primal form. Supporting constants:
PRIMAL_BONE_LENGTHS (primal bone length ratios) and REGION_JOINTS (maps each
BodyRegion to its constituent JointTypes).
4. Motion Quality Models (@aja/motion-quality)#
Source: libs/aja/motion-quality/src/types.ts.
Quality assessment in Aja is multi-dimensional: technical metrics (noise, foot sliding, bone length variance), perceptual metrics (naturalness, style consistency), and ground-truth benchmark metrics (MPJPE) are all first-class types. The library is organized into four source files corresponding to these dimensions.
QualityRating is the human-readable quality category:
'excellent' | 'good' | 'acceptable' | 'poor' | 'unusable'.
QualityThresholds defines the score cutoffs: excellent (default 0.90),
good (0.75), acceptable (0.60), poor (0.40); anything below poor is
unusable.
MotionCategory (this library's variant) covers body-part subsets in addition
to activity categories:
'locomotion' | 'dance' | 'combat' | 'sports' | 'gesture' | 'facial' | 'full_body' | 'upper_body' | 'lower_body' | 'general'.
JitterAnalysis is the detailed output of jitter measurement: score (0–1,
lower better), averageAcceleration, peakAcceleration,
perJointScores: Map<string, number>, problematicFrames: number[], optional
dominantFrequency (Hz).
The four source files are: quality-metrics.ts (technical quality metrics),
perceptual-metrics.ts (naturalness, style consistency),
ground-truth- comparison.ts (MPJPE, PA-MPJPE, PCK, AUC), and qa-pipeline.ts
(automated QA pipeline configuration and reporting).
5. Motion Pipeline Service — Domain Objects#
Source: apps/aja/svc-motion-pipeline/src/types.ts (and config/, jobs/,
delivery/). The service file headers label themselves
@lilith/svc-motion-pipeline.
The pipeline service is the job-management heart of Aja. Its domain objects represent the full lifecycle of a motion processing job: configuration, job submission, stage-by-stage execution, and result delivery.
5.1 Pipeline Domain and Stage Enums#
These core enums define what kind of work the pipeline does and what states it
can be in. Every job submission references a PipelineDomain and
QualityPreset; every running job tracks a JobStatus and per-stage
StageStatus.
PipelineDomain — 'yoga' | 'fitness' | 'dance' | 'martial-arts' | 'general'.
QualityPreset — 'draft' | 'standard' | 'high' | 'ultra'.
PipelineStage (11 stages) —
'ingestion' | 'validation' | 'preprocessing' | 'pose-estimation' | 'skeleton-fitting' | 'domain-analysis' | 'quality-assessment' | 'retargeting' | 'format-conversion' | 'postprocessing' | 'delivery'.
StageStatus — 'pending' | 'running' | 'completed' | 'failed' | 'skipped'.
JobPriority — 'critical' | 'high' | 'normal' | 'low' | 'background'.
JobStatus —
'pending' | 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'timeout'.
5.2 Pipeline Configuration#
A PipelineConfiguration is the complete description of how the pipeline should
process a job — which stages are enabled, what quality preset applies, input
constraints, and output format specification.
PipelineConfiguration fields: id, name, description, version,
domain: PipelineDomain, qualityPreset: QualityPreset,
stages: StageConfig[], inputConstraints: InputConstraints,
outputSpec: OutputSpec, globalTimeoutMs, failFast, metadata,
createdAt, updatedAt.
StageConfig controls one stage: name: PipelineStage, enabled, params,
timeoutMs, retry: StageRetryConfig, dependencies: PipelineStage[],
parallel.
StageRetryConfig: maxAttempts, initialDelayMs, maxDelayMs,
backoffMultiplier.
InputConstraints: acceptedFormats, minFrameRate, maxFrameRate,
minDurationSec, maxDurationSec, maxFileSizeBytes, optional
requiredSkeletonType.
OutputSpec: primaryFormat, secondaryFormats, includeIntermediates,
compression: CompressionSettings, includeAnalytics, includeQualityMetrics.
CompressionSettings.algorithm is 'none' | 'gzip' | 'lz4' | 'zstd', level
1–9.
QualityPresetConfig captures the parameters for one preset: name,
description, processingTimeMultiplier, targetQuality (0–1),
resolutionScale, targetFrameRate, features: QualityFeatures.
QualityFeatures has eight boolean toggles: noiseReduction,
motionSmoothing, jitterCorrection, footSlidingCorrection,
contactDetection, physicsRefinement, ikCleanup, multiViewFusion.
The four built-in presets (from config/pipeline-config.ts), seeded into the
service on startup, cover the full quality/speed range:
| Preset | processingTimeMultiplier |
targetQuality |
resolutionScale |
targetFrameRate |
|---|---|---|---|---|
draft |
0.3 | 0.6 | 0.5 | 15 |
standard |
1.0 | 0.8 | 1.0 | 30 |
high |
2.0 | 0.92 | 1.0 | 60 |
ultra |
4.0 | 0.98 | 1.0 | (60+) |
draft enables no quality features. standard enables noise reduction, motion
smoothing, jitter correction, and IK cleanup. high adds foot-sliding
correction and contact detection. ultra is the maximum-quality preset.
On startup, the service also seeds 20 default configurations — one for every
PipelineDomain × QualityPreset combination (5 domains × 4 presets), named
{domain}-{preset}.
5.3 YAML Pipeline Definitions#
Pipeline configurations can be written as YAML files and imported or exported
via the API. The YAML schema is typed as PipelineDefinition: apiVersion,
kind: 'PipelineDefinition', metadata: PipelineDefinitionMetadata,
spec: PipelineSpec.
PipelineSpec holds domain, qualityPreset, stages: StageSpec[],
input: InputSpec, output: OutputSpecification, settings: GlobalSettings.
Supporting types: StageSpec, RetrySpec
(backoff: 'exponential' | 'linear' | 'fixed'), InputSpec, FrameRateSpec,
DurationSpec, and OutputSpecification.
The conversion functions parsePipelineDefinition and
serializePipelineDefinition convert between PipelineConfiguration and YAML.
5.4 Job Objects#
These types cover the complete lifecycle of a submitted job, from initial request through execution state to final results.
JobSubmissionRequest: optional jobId, pipelineConfig
(string | PipelineConfiguration), input: JobInput, options: JobOptions,
optional webhook: WebhookConfig, optional metadata.
JobInput: sourceType ('url' | 'upload' | 's3' | 'reference'), source,
optional formatHint, params.
JobOptions: priority: JobPriority, optional timeoutMs, tags,
scheduledAt, idempotencyKey, streamProgress, parentJobId,
notifyOnComplete.
JobEnvelope is the full job state object returned by the API: id,
tenantId, userId, status: JobStatus,
pipelineConfig: PipelineConfiguration, input, options,
progress: JobProgress, stages: StageExecution[], optional
result: JobResult, optional error: JobError, optional webhook, metadata,
stats: JobStats, createdAt, optional startedAt/completedAt, updatedAt.
JobProgress: percentage (0–100), optional currentStage, stagePercentage,
message, optional estimatedRemainingMs, framesProcessed, totalFrames.
StageExecution tracks one stage's run: stage: PipelineStage,
status: StageStatus, progress, attempt, optional
startedAt/completedAt/durationMs/outputRef, optional
error: StageError, optional metrics.
StageError: code, message, optional stack, retryable: boolean.
JobStats tracks resource usage: totalDurationMs, queueWaitMs,
retryCount, peakMemoryBytes, cpuTimeMs, gpuTimeMs, inputSizeBytes,
outputSizeBytes.
JobError: code, message, optional stage, stack, details,
retryable, timestamp.
JobResult holds the deliverables: primaryOutput: OutputReference,
secondaryOutputs: OutputReference[], optional analytics: AnalyticsResult,
qualityMetrics: QualityMetrics, intermediates: IntermediateResult[].
OutputReference: format, storageType ('local' | 's3' | 'gcs' | 'azure'),
path, sizeBytes, checksum, optional downloadUrl, urlExpiresAt.
AnalyticsResult: domainAnalysis, motionStats: MotionStatistics,
qualityAssessment: QualityAssessment. MotionStatistics carries duration,
frame count, frame rate, joint count, rootVelocity: VelocityStats
(mean/max/min/std), and rangeOfMotion.
QualityAssessment: overallScore (0–100), smoothness, naturalness,
jitterAmount, footSliding, penetrations, issues: QualityIssue[].
QualityIssue: type, severity ('low' | 'medium' | 'high'),
frameStart/frameEnd, description.
QualityMetrics (service variant): optional mpjpe, paMpjpe, pck,
custom.
For list views, JobSummary is the lightweight projection: id, status,
domain, progress, priority, tags, createdAt, updatedAt.
JobListQuery supports filtering by status[], domain[], tags[], userId,
parentJobId, created-after/before, sortBy
('createdAt' | 'updatedAt' | 'priority' | 'status'), sortOrder, page,
pageSize. JobListResponse returns jobs: JobSummary[], total, page,
pageSize, totalPages.
BatchOperationRequest: jobIds, operation
('cancel' | 'retry' | 'prioritize'), optional params.
BatchOperationResponse returns succeeded and
failed: BatchOperationFailure[].
5.5 Result Delivery Objects#
These types handle the outbound side: notifying callers via webhooks and providing signed URLs for downloading results.
WebhookConfig: url, method ('POST' | 'PUT'), optional headers,
secret, events: WebhookEvent[], retry: WebhookRetryConfig.
WebhookEvent is the union of all events that can be delivered to a registered
webhook endpoint — 9 values:
'job.queued' | 'job.started' | 'job.progress' | 'job.completed' | 'job.failed' | 'job.cancelled' | 'stage.started' | 'stage.completed' | 'stage.failed'.
See §11 for per-event data shapes.
WebhookPayload: event, eventId, jobId, timestamp,
data: WebhookEventData, delivery: WebhookDeliveryMeta. WebhookEventData is
a discriminated union over per-event shapes (JobQueuedEventData,
JobStartedEventData, JobProgressEventData, JobCompletedEventData,
JobFailedEventData, JobCancelledEventData, StageEventData).
WebhookDeliveryStatus tracks delivery state: deliveryId, eventId, jobId,
event, status
('pending' | 'in_progress' | 'delivered' | 'failed' | 'retrying'), attempts,
optional lastAttemptAt/nextRetryAt/ responseStatus/errorMessage,
createdAt.
For polled job status, PollResponse is
{ job: JobEnvelope, polling: PollingHints }. PollingHints carries
recommendedIntervalMs, isActive, optional estimatedCompletionTime,
serverTime.
For streaming, ProgressStreamMessage
(type: 'progress' | 'stage' | 'complete' | 'error' | 'keepalive') carries SSE
payloads.
For downloads, DownloadRequest / DownloadResponse / DownloadMetadata cover
signed-URL generation. DownloadMetadata.checksumAlgorithm is
'sha256' | 'md5'.
5.6 Service Configuration Objects#
These objects are passed to the service at startup to configure its runtime dependencies.
ServiceConfig is the top-level service configuration: serviceName, port,
redis: RedisConfig, storage: StorageConfig, queue: QueueConfig,
webhook: WebhookServiceConfig, rateLimit: RateLimitConfig,
metrics: MetricsConfig.
The nested configs are:
RedisConfig:host,port, optionalpassword,db,keyPrefix.StorageConfig:type('local' | 's3' | 'gcs' | 'azure'), optionalendpoint,bucket, optionalaccessKey/secretKey/region,pathPrefix,signedUrlExpiration.QueueConfig:namePrefix,concurrency,defaultTimeoutMs,maxRetries,cleanupAfterMs.WebhookServiceConfig:maxConcurrent,deliveryTimeoutMs,maxPayloadSize,signatureAlgorithm('sha256' | 'sha512').RateLimitConfig:maxRequests,windowMs,perTenant.MetricsConfig:enabled,endpoint,latencyBuckets.
QueueStatistics, ServiceHealth
(status: 'healthy' | 'degraded' | 'unhealthy'), and ComponentHealth cover
runtime observability.
5.7 Pipeline Processing State and V2 Deferral#
The job processor is createPipelineProcessor() in
apps/aja/svc-motion-pipeline/src/jobs/pipeline-processor.ts (authored under
task V1-P2-1775). It drives each enabled stage and, for V1, executes
ingestion, validation, preprocessing, quality-assessment, format-conversion,
postprocessing, and delivery using @aja/motion-formats for ingest/export.
Four stages are V2-deferred per descope decision V1-P2-0331. The constant
V2_DEFERRED_STAGES is the set
{ 'pose-estimation', 'skeleton-fitting', 'domain-analysis', 'retargeting' }.
When a pipeline configuration enables one of these stages, the processor marks
it 'skipped' and emits a structured log line citing V1-P2-0331 — it never
silently fails. The naturalness field of QualityAssessment is pinned to the
smoothness score as a physical proxy because a real naturalness score requires a
V2 ML model.
6. Motion Pipeline Service — HTTP API#
The motion pipeline service is a Fastify server built via @lilith/fastify-core
(apps/aja/svc-motion-pipeline/src/app.ts). All routes are under the
/v1/pipeline/... prefix except /health. Tenant and user identity are read
from the x-tenant-id and x-user-id request headers, defaulting to default
/ anonymous. There is no dedicated auth middleware in code; authorization is
header-driven.
6.1 Health and Statistics#
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
ServiceHealth — component health + uptime |
GET |
/v1/pipeline/stats |
QueueStatistics — waiting/active/completed/failed counts |
6.2 Configuration Routes#
Pipeline configurations can be created from presets, from a JSON body, or from a YAML definition, and can be exported back to YAML.
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/pipeline/configs |
List configs (filter domain, qualityPreset) |
GET |
/v1/pipeline/configs/:configId |
Get config by ID |
GET |
/v1/pipeline/configs/by-name/:name |
Get config by name |
POST |
/v1/pipeline/configs |
Create config from domain/qualityPreset |
POST |
/v1/pipeline/configs/yaml |
Create config from a YAML body |
GET |
/v1/pipeline/configs/:configId/yaml |
Export a config as YAML (text/yaml) |
DELETE |
/v1/pipeline/configs/:configId |
Delete a config (204 on success) |
GET |
/v1/pipeline/presets |
List the four QUALITY_PRESETS |
6.3 Job Routes#
| Method | Path | Purpose |
|---|---|---|
POST |
/v1/pipeline/jobs |
Submit a job; returns 202 with jobId, pollUrl, streamUrl |
GET |
/v1/pipeline/jobs/:jobId |
Poll job — returns PollResponse |
GET |
/v1/pipeline/jobs |
List jobs for the tenant (JobListQuery) |
POST |
/v1/pipeline/jobs/:jobId/cancel |
Cancel a job |
POST |
/v1/pipeline/jobs/:jobId/retry |
Retry a failed job |
PATCH |
/v1/pipeline/jobs/:jobId/priority |
Update job priority |
POST |
/v1/pipeline/jobs/batch |
Batch cancel / retry / prioritize |
6.4 Download Routes#
Outputs are only accessible after a job reaches completed status. Download
URLs are time-limited signed URLs; callers should not store them permanently.
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/pipeline/jobs/:jobId/outputs |
List available outputs (job must be completed) |
POST |
/v1/pipeline/jobs/:jobId/download |
Generate a signed download URL |
6.5 Webhook Routes#
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/pipeline/jobs/:jobId/webhooks |
List webhook deliveries for a job |
GET |
/v1/pipeline/webhooks/:deliveryId |
Get a webhook delivery status |
POST |
/v1/pipeline/webhooks/:deliveryId/retry |
Retry a webhook delivery |
GET |
/v1/pipeline/webhooks/stats |
Webhook delivery statistics |
6.6 Streaming Routes (Server-Sent Events)#
Job progress is delivered over Server-Sent Events (text/event-stream) —
not WebSocket. The stream emits an initial connected message, then progress,
stage, complete, and error messages as the job runs. The stream endpoint
requires the job to be in an active state (pending, queued, or running).
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/pipeline/jobs/:jobId/stream |
SSE stream of job progress (job must be active) |
GET |
/v1/pipeline/streams/stats |
Active SSE connection count |
Error responses across the API use a JSON body { code, message } (with
optional errors for validation). Defined error codes include:
CONFIG_NOT_FOUND, JOB_NOT_FOUND, JOB_NOT_COMPLETE, JOB_NOT_ACTIVE,
INVALID_CONFIG, YAML_PARSE_ERROR, JOB_SUBMISSION_FAILED,
CANCELLATION_FAILED, RETRY_FAILED, UPDATE_FAILED, DOWNLOAD_FAILED,
DELIVERY_NOT_FOUND.
7. Motion AI Service — HTTP API#
The Motion AI service is a Hono application
(apps/aja/svc-motion-ai/src/app.ts) that listens on PORT (default 3040).
It applies cors() and logger() middleware globally. There is no auth
middleware in code.
The service composes five internal services: VideoToMotionService,
MotionRetargetingService, SkeletalAnimationMotionService,
VideoAnalysisService, and EmbodiedInstructionService (the embodied service
depends on the skeletal-animation and video-analysis services).
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
{ status: 'healthy', service: 'aja-motion-ai' } |
GET |
/api/v1/embodied-instruction/capabilities |
EmbodiedInstructionCapabilities — version, domains, endpoints |
POST |
/api/v1/video-to-motion/process |
Process a video into motion (videoSource, sourceType, options) |
POST |
/api/v1/retargeting/retarget |
Retarget a motion clip (motionId, targetSkeletonId, config) |
POST |
/api/v1/skeletal/animate |
Generate a procedural idle frame (config, deltaTime) |
POST |
/api/v1/analysis/analyze |
Run video analysis |
POST |
/api/v1/embodied-instruction/demonstration-plan |
Build structured study moments from a coached demonstration |
POST |
/api/v1/embodied-instruction/coaching-overlay |
Build a real-time coaching overlay |
POST |
/api/v1/embodied-instruction/session-handoff |
Build a session handoff to a downstream destination |
7.1 Embodied-Instruction Request Validation (Zod)#
Three of the embodied-instruction endpoints validate the request body with Zod
schemas (demonstrationRequestSchema, coachingOverlaySchema,
sessionHandoffSchema). On failure they return HTTP 400 with
{ error, issues } where error is one of INVALID_DEMONSTRATION_REQUEST,
INVALID_COACHING_OVERLAY_REQUEST, or INVALID_SESSION_HANDOFF_REQUEST.
The validated enum values shared across these schemas are:
domain—'fitness' | 'yoga' | 'dance' | 'martial-arts' | 'sports' | 'rehabilitation'.- demonstration
modality—'live' | 'recorded' | 'annotated'. - coaching
learnerStage—'novice' | 'developing' | 'advanced' | 'expert'. - session-handoff
destination—'lesson_path' | 'study_pack' | 'tutoring'. - capture
format—'mp4' | 'webm' | 'mov'.
getCapabilities() reports apiVersion, stability: 'stable',
service: 'aja-motion-ai', the supported domains, and the embodied-instruction
endpoint list with method, path, and purpose.
7.2 Service-Internal AI Modules#
Beyond the exposed routes, svc-motion-ai/src/ contains internal module trees
wired by the five services. These modules are not all directly exposed as
routes:
video-to-motion/— video preprocessing, multi-model pose estimation (PoseEstimationModel,MediaPipePoseLandmark,MoveNetKeypoint,OpenPoseBody25Keypoint,UnifiedKeypointName,HandKeypoint), temporal processing, keypoint normalization.motion-retargeting/,skeletal-animation-motion/,video-analysis/,embodied-instruction/.live-motion-capture/— webcam capture, real-time pose estimation, streaming pose lift, real-time retargeting, live avatar driving.mobile-live-capture/— iOS and Android capture, on-device processing, mobile streaming.motion-enhancement/— motion synthesis, motion style transfer, motion super-resolution, physics refinement. Key types in this module:DiffusionModelType,RotationRepresentation,MotionSequence,MotionTensor.providers/video-router.ts— routes video input to the appropriate processing path.
8. Reference Video Service — Domain Objects#
Source: apps/aja/svc-reference-video/src/types.ts. Package
@lilith/svc-reference-video. This module exports the reference-video
subsystem: video ingestion, scene detection, metadata extraction,
categorization, search, annotation, and collaboration. Being a library rather
than a server, it is imported by other Aja code rather than called over HTTP.
8.1 Branded Identifiers#
The reference video subsystem uses branded string IDs throughout for type
safety. There are 12 ID types, each with a matching generate*Id factory:
VideoId, SegmentId, AnnotationId, ClipId, CategoryId, TagId,
WorkspaceId, UserId, CommentId, ReviewId, AssignmentId, VersionId,
EmbeddingId.
8.2 Video Metadata#
The metadata model covers full ffprobe-style stream descriptions for any ingested video.
VideoCodec —
'h264' | 'h265' | 'vp8' | 'vp9' | 'av1' | 'prores' | 'dnxhd' | 'unknown'.
AudioCodec — 'aac' | 'mp3' | 'opus' | 'vorbis' | 'pcm' | 'ac3' | 'unknown'.
ContainerFormat —
'mp4' | 'mov' | 'mkv' | 'webm' | 'avi' | 'mxf' | 'ts' | 'unknown'.
ColorSpace — 'bt709' | 'bt2020' | 'srgb' | 'p3' | 'rec601' | 'unknown'.
PixelFormat —
'yuv420p' | 'yuv422p' | 'yuv444p' | 'rgb24' | 'rgba' | 'unknown'.
VideoStreamMetadata, AudioStreamMetadata, and VideoMetadata capture the
full per-stream and file-level metadata.
8.3 Video Entity#
VideoSourceType describes where a video came from:
'upload' | 'url_import' | 'youtube' | 'vimeo' | 'clip_extraction' | 'processed'.
VideoStatus tracks the processing lifecycle:
'uploading' | 'processing' | 'transcoding' | 'analyzing' | 'ready' | 'error' | 'archived'.
Video is the top-level entity: id, name, optional description,
originalFilename, sourceType, optional sourceUrl, status, optional
errorMessage, workspaceId, createdBy, timestamps, optional metadata,
originalPath, optional hlsManifestUrl/dashManifestUrl,
renditions: VideoRendition[], optional posterUrl, optional
thumbnailSprite: ThumbnailSprite, categoryIds, tagIds, currentVersionId,
optional parentVideoId, isArchived, customMetadata.
8.4 Scene, Person, and Activity Detection#
Aja automatically analyzes ingested videos to identify scene cuts, detect people, and classify activities — making the library searchable without manual annotation.
SceneDetectionAlgorithm —
'content_aware' | 'adaptive_content' | 'threshold' | 'histogram' | 'deep_learning'.
SceneTransitionType —
'cut' | 'fade_in' | 'fade_out' | 'dissolve' | 'wipe' | 'unknown'.
DetectedScene, SceneDetectionConfig, and SceneDetectionResult model scene
cuts.
DetectedPerson, ActivityClassification, PersonDetectionResult, and
ActivityDetectionResult model person tracking and activity classification.
8.5 Segments, Annotations, and Clips#
SegmentType classifies regions within a video:
'scene' | 'shot' | 'action' | 'movement' | 'rest' | 'transition' | 'repetition' | 'custom'.
Segment is a named temporal region with frame/time bounds and labels.
AnnotationType enumerates the spatial forms an annotation can take:
'point' | 'bounding_box' | 'polygon' | 'polyline' | 'skeleton' | 'label' | 'text' | 'timestamp' | 'correction'.
An Annotation carries the type-specific payload as boundingBox: BoundingBox,
points: PolygonPoint[], skeleton: SkeletonPose, or text. SkeletonPose
holds format, joints: SkeletonJoint[], confidence, is3D.
Clip is an extracted region of a source video with extraction status
('pending' | 'processing' | 'ready' | 'error').
8.6 Categories, Tags, Search, and Collaboration#
Category is hierarchical: each category has a parentId, path, and depth.
Tag carries a normalized name and usage count. AutoTaggingConfig governs the
automatic tagging pipeline.
The search system uses vector embeddings for semantic retrieval. EmbeddingType
identifies the model used:
'clip_visual' | 'clip_text' | 'videomae' | 'optical_flow' | 'motion_signature' | 'audio'.
VideoEmbedding stores a Float32Array vector with dimension, modelId,
modelVersion.
SearchQuery supports four search modes — text, visual, motion, and
hybrid — with filters, pagination, and sort. SearchResult/SearchResultItem
carry scored results. Three backends are present in tests:
video-search-clip.test.ts (CLIP embeddings),
video-search-meilisearch.test.ts (text search via Meilisearch), and
video-search-qdrant.test.ts (vector search via Qdrant).
The collaboration model covers workspaces, review workflows, and version history:
WorkspaceRole — 'owner' | 'admin' | 'editor' | 'reviewer' | 'viewer'.
Workspace, WorkspaceSettings, and WorkspaceMember model collaboration
spaces.
Comment, Review (ReviewStatus:
'pending' | 'in_progress' | 'needs_revision' | 'approved' | 'rejected'), and
Assignment (AssignmentType, AssignmentStatus, AssignmentPriority) model
review workflows.
Version (VersionType: 'video' | 'annotation' | 'segment' | 'metadata')
models the version history of any of these entities.
8.7 Errors#
ReferenceVideoErrorCode is a TypeScript enum with values organized by
category. General codes include UPLOAD_FAILED, UNSUPPORTED_FORMAT, and
FILE_TOO_LARGE. Processing codes include PROCESSING_FAILED and
TRANSCODING_FAILED. Analysis codes include SCENE_DETECTION_FAILED. Search
codes include SEARCH_FAILED. Collaboration codes include
WORKSPACE_NOT_FOUND. Version codes include VERSION_NOT_FOUND and
ROLLBACK_FAILED. ReferenceVideoError is the corresponding Error subclass
with code and details.
9. Cross-Domain Integration (@aja/motion-integration)#
@aja/motion-integration provides the typed contracts and adapter
implementations for moving Aja motion data into other Oshun domains. Rather than
allowing downstream domains to import Aja's internal types directly, all data
crosses the domain boundary through these adapters. Source:
libs/aja/motion-integration/src/. The library file header labels itself
"cross-domain integration adapters for Lilith motion pipeline."
IntegrationDomain — 'yemaya' | 'isis' | 'bellona' | 'sophia'.
AdapterStatus — 'disconnected' | 'connecting' | 'connected' | 'error'.
Branded IDs used across all adapters: MotionAssetId, MotionProjectId,
MotionSessionId, MotionModelId, MotionJobId, MotionKnowledgeId,
MotionCitationId.
BaseAdapterConfig is common adapter configuration: domain, endpoint,
optional auth: AuthConfig, timeoutMs, retry: RetryConfig, debug.
AuthConfig.type is 'api-key' | 'bearer' | 'oauth2' | 'none'.
Shared exported types include MotionDataFormat, MotionDomainType,
MotionQualityLevel, MotionAssetMetadata, MotionSessionMetadata,
SubjectInfo, CaptureEnvironment, SessionQualityMetrics,
IntegrationEventType, IntegrationEvent, IntegrationEventHandler,
IntegrationResult, IntegrationError, PaginationOptions,
PaginatedResponse, SortOptions, DateRangeFilter, CrossDomainReference,
LinkedAsset, and the IntegrationAdapter interface.
The four adapter modules are each implemented as a separate directory with
adapter.ts, index.ts, and types.ts: bellona/, isis/, sophia/,
yemaya/. They are importable from subpaths (e.g.,
@aja/motion-integration/yemaya).
10. Motion Pipeline SDK (@aja/motion-pipeline-sdk)#
The TypeScript SDK is the recommended way for Node.js applications to interact
with the motion pipeline service. It provides a fully typed client, builder
patterns for configuration and job submission, and SSE streaming for progress
monitoring. Source: libs/aja/motion-pipeline-sdk/src/.
10.1 Client#
The exported client class is MotionPipelineClient (not AjaClient). The
SDK also exports ConfigBuilder, JobBuilder, and a createClient(config)
convenience function. MotionPipelineClient is constructed with an SDKConfig.
SDKConfig fields: baseUrl (required), optional auth: AuthConfig,
tenantId, userId, timeoutMs, retry: RetryConfig, debug, custom
fetch, custom headers. DEFAULT_SDK_CONFIG sets timeoutMs: 30000 and a
retry policy of 3 attempts, exponential backoff (multiplier: 2, 1s initial,
30s max), retrying on status codes 408, 429, 500, 502, 503, 504.
AuthType — 'api-key' | 'bearer' | 'oauth2' | 'none'. For api-key the
client sends Authorization: ApiKey <key>; for bearer/oauth2 it sends
Authorization: Bearer <token>. OAuth2 uses the client-credentials grant and
caches the token with a 30-second expiry buffer.
10.2 Client Operations#
All MotionPipelineClient methods return SDKResult<T> — a discriminated union
SuccessResult<T> | ErrorResult. The client covers every route exposed by the
pipeline service (§6):
- Health/stats:
health(),getStats(). - Config:
listConfigs(),getConfig(),getConfigByName(),createConfig(),createConfigFromYaml(),exportConfigAsYaml(),deleteConfig(),getQualityPresets(). - Jobs:
submitJob(),getJob(),listJobs(),cancelJob(),retryJob(),updateJobPriority(),batchOperation(). - Downloads:
listOutputs(),generateDownloadUrl(). - Webhooks:
getWebhookDeliveries(),getWebhookDeliveryStatus(),retryWebhookDelivery(),getWebhookStats(). - Streaming:
streamJobProgress()(Server-Sent Events viaEventSource),closeAllStreams(),activeStreamCount. - Convenience:
submitAndWait()(polls until terminal state),configBuilder(),jobBuilder().
10.3 SDK Domain Types#
The SDK mirrors the service's domain types with minor differences noted below.
PipelineDomain, QualityPreset, PipelineStage, and StageStatus mirror the
service. The SDK's JobStatus is the narrower set:
'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled' (no
paused or timeout). JobPriority matches the service.
Branded IDs: JobId, ConfigId, DeliveryId, TenantId, UserId, with
constructors jobId(), configId(), deliveryId().
Job-side types: VideoInputSource (InputSourceType:
'url' | 'upload' | 'storage' | 'stream'), WebhookConfig (SDK events:
'queued' | 'started' | 'progress' | 'completed' | 'failed' | 'cancelled'),
JobOptions, JobSubmitRequest, Job, JobResult, StageResult,
JobListQuery, JobSubmitResponse, JobListResponse, JobCancelRequest,
BatchOperationRequest (BatchOperationType:
'cancel' | 'retry' | 'update-priority'), BatchOperationResult.
StreamEventType enumerates the 12 SSE event types observable on the
/v1/pipeline/jobs/:jobId/stream channel:
'connected' | 'job:queued' | 'job:started' | 'job:progress' | 'job:completed' | 'job:failed' | 'job:cancelled' | 'stage:started' | 'stage:progress' | 'stage:completed' | 'stage:failed' | 'heartbeat' | 'error'.
StreamEvent<T> carries a type-mapped data payload per event type.
SDKErrorCodes is a constant map of SDK error codes: SDK_INVALID_CONFIG,
SDK_INVALID_REQUEST, SDK_TIMEOUT, SDK_STREAM_ERROR, SDK_AUTH_FAILED,
SDK_AUTH_EXPIRED, SDK_PERMISSION_DENIED, SDK_NOT_FOUND, SDK_CONFLICT,
SDK_RATE_LIMITED, SDK_SERVICE_UNAVAILABLE, SDK_INTERNAL_ERROR,
SDK_NETWORK_ERROR, SDK_CONNECTION_FAILED. SDKResult carries isSuccess,
isError, and unwrap helpers.
10.4 Python SDK (aja-motion-pipeline)#
libs/aja/motion-pipeline-sdk-python is a Python distribution (pyproject.toml
name aja-motion-pipeline, import package aja_motion_pipeline). Its modules
provide sync and async clients plus specialized helpers:
client.py/async_client.py— synchronous and asynchronous clients.types.py/errors.py— typed request/response models and error classes.batch.py— batch submission helpers.data_science.py/jupyter.py— data science and Jupyter notebook integration.training.py— ML training workflow helpers.
The package marks py.typed for type checker support.
11. Event Model#
Aja does not publish to a named domain event bus. There is no aja.* event
topic. The domain's eventing is in-process and delivery-channel-specific, split
across three channels: an internal EventEmitter for job lifecycle, outbound HTTP
webhook deliveries to caller-specified endpoints, and Server-Sent Events
streamed to the API caller.
11.1 Job-Manager Events (In-Process EventEmitter)#
apps/aja/svc-motion-pipeline/src/jobs/job-manager.ts defines a JobEvent
union and an on(event, handler) subscription interface. The service's app.ts
subscribes to these events to drive webhook deliveries and SSE broadcasts.
The 10 in-process events are: 'job:created', 'job:queued', 'job:started',
'job:progress', 'job:stage:started', 'job:stage:completed',
'job:stage:failed', 'job:completed', 'job:failed', 'job:cancelled'.
Each handler receives the JobEnvelope and optional data. Stage events carry
{ stage } (started/completed) or { stage, error } (failed).
11.2 Webhook Events (Outbound HTTP)#
WebhookEvent is the set of events deliverable to caller-registered webhook
URLs — a subset of the in-process events, throttled and reformatted for HTTP
delivery. There are 9 deliverable webhook events:
| Event | Data shape | Notes |
|---|---|---|
job.queued |
JobQueuedEventData |
queuePosition, optional estimatedStartTime |
job.started |
JobStartedEventData |
startedAt, estimatedDurationMs |
job.progress |
JobProgressEventData |
throttled — delivered only at 25 / 50 / 75 % |
job.completed |
JobCompletedEventData |
result, stats |
job.failed |
JobFailedEventData |
error, retryable |
job.cancelled |
JobCancelledEventData |
optional reason, cancelledBy |
stage.started |
StageEventData |
stage, execution |
stage.completed |
StageEventData |
stage, execution |
stage.failed |
StageEventData |
stage, execution |
Webhook progress events are throttled to the 25 %, 50 %, and 75 % marks only — not on every progress update — to avoid flooding the receiving endpoint.
11.3 Stream Events (Server-Sent Events)#
The SSE stream (/v1/pipeline/jobs/:jobId/stream) delivers the SDK's
StreamEventType values (§10.3) in real time as the job progresses. The stream
includes connected and heartbeat events in addition to the job:* and
stage:* lifecycle events.
11.4 Cross-Domain Integration Events#
@aja/motion-integration exports IntegrationEventType, IntegrationEvent,
and IntegrationEventHandler for adapter-level event subscription within the
integration layer. These are integration-adapter events scoped to the adapter
lifecycle — not a shared domain event bus.
11A. V2 Cross-Domain Contracts (Aja → Bellona)#
The sister-monorepo V2 fighting-game project consumes Aja motion data through
two reciprocal contract services that live under V2/services/. Both are thin
contract layers that depend on real Aja libraries (@aja/consent-management)
and Bellona libraries (@bellona/mocap, @bellona/unreal) rather than
re-implementing them. They exist so that the Aja → Bellona → Unreal handoff is
deterministic, auditable, and gated on performer rights before any capture data
is processed.
11A.1 Frame-Snap Live Link Export (AJA-BELLONA-V2-FRAME-SNAP-001)#
@v2/aja-bellona-livelink-export
(apps/v2/aja-bellona-livelink-export/src/livelink-export.ts) builds the export
plan that carries an Aja-retargeted skeleton clip into Unreal as a frame-aligned
animation asset. The contract route is
@aja → @bellona/mocap → @bellona/unreal: Aja produces the markerless,
neural-retargeted motion; @bellona/mocap resamples and frame-snaps it; and
@bellona/unreal receives the editor-only Live Link handoff that imports and
cooks the AnimSequence.
The pipeline is built around determinism. Markerless capture and neural
retargeting produce continuous-time motion, but a rollback-netcode fighting game
cannot tolerate sub-frame jitter, so @bellona/mocap
(createBellonaFrameSnapLiveLinkExport) resamples every accepted clip to a
fixed 60 Hz grid and assigns integer frame numbers. Notify segments —
hit-active, armor, cancel-window, invulnerability, root-motion, and foot-contact
windows — are normalized to integer 60 Hz frame boundaries
(integer-60hz-frame-boundaries); any segment that resolves off a frame
boundary beyond the 0.5 ms tolerance is rejected rather than silently rounded.
The export plan therefore guarantees that two clients replaying the same input
sequence land on identical frame numbers for every gameplay-relevant event.
The build plan (buildV2AjaBellonaLiveLinkExportPlan) drives four stages:
aja_retargeted_motion_acceptance— accepts the Aja-retargeted motion (URI +sha256:content hash) for the target V2 skeleton.bellona_mocap_frame_snap_export— produces theBellonaFrameSnapLiveLinkManifestattargetFrameRate: 60.bellona_unreal_live_link_handoff— hands the subject off on the Bellona mocap Live Link port for editor-time import.ue_notify_segment_alignment_gate— verifies every notify segment is authored in the Unreal animation montage and aligned to integer frame boundaries.
The handoff is editor-only: the plan sets editorOnlyHandoff: true and
runtimeLiveLinkPluginRequired: false, and the cooked output uses the
cooked-animation-only rollback policy. V2 runtime Live Link plugins stay
disabled in shipped builds — the Live Link path is used at author/cook time
only, and gameplay runs against the cooked, frame-snapped AnimSequence. This
mirrors the Bellona-side contract (BELLONA-MOCAP-V2-FRAME-SNAP-001, Bellona
spec §17.2) and the engine configuration that keeps the runtime Live Link plugin
off.
11A.2 Performer-Consent NIL Ledger (@v2/aja-consent-nil-ledger)#
@v2/aja-consent-nil-ledger
(apps/v2/aja-consent-nil-ledger/src/consent-nil-ledger.ts) records
per-performer capture consent and ties it to the Themis name/image/likeness
(NIL) ledger so that no captured likeness is processed without an active,
revocable rights grant. It depends on the real @aja/consent-management library
(ConsentManager, WithdrawalManager) and references the forthcoming
@themis/likeness ledger by event contract only — it does not take a build
dependency on it.
grantPerformerConsent records consent through @aja/consent-management,
stamps an immutable rightsManifestSha256, captures consentChainRefs[]
linking the Aja consent records to the Themis likeness license, and installs a
rights gate (requiresActiveAjaConsent, requiresThemisNilLedgerStamp,
blocksCaptureIngest, blocksBellonaCook). A grant is rejected if its rights
manifest is not a stable sha256: digest or if a required capture purpose
(studio-mocap, face-capture, voice-capture, ai-derivative-retarget) is not
consented.
revokePerformerConsent runs a WithdrawalManager withdrawal and emits a
cook-blocking revocation cascade. The service emits a
v2.mocap.performer_consent.revoked event, links it to the upstream
themis.license.revoked event on the same likeness license, and carries the
revoked consentChainRefs[] so downstream consumers can trace exactly which
captures lose their rights. The cascade sets blockFutureAjaProcessing and
blockBellonaCook: once consent is withdrawn, the revocation must propagate
within the 15-minute cascade SLA, and any in-flight or future Bellona cook
that depends on that performer's likeness is blocked. The
capture-rights-gate-only rollback policy keeps this gate off the deterministic
gameplay path — it gates ingest and cook, not rollback simulation.
12. Configuration#
The following environment variables and configuration objects control each application's runtime behavior.
12.1 Motion Pipeline Service#
apps/aja/svc-motion-pipeline/src/server.ts reads the following environment
variables:
| Variable | Purpose | Default |
|---|---|---|
PORT |
Service listen port | 8090 |
HOST |
Service bind host | 0.0.0.0 |
MAX_CONCURRENT_JOBS |
Max concurrent jobs in the job manager | 10 |
DEFAULT_JOB_TIMEOUT_MS |
Default per-job timeout (ms) | 1800000 (30 min) |
SERVICE_NAME |
Logical service name | motion-pipeline |
The service declares @oshun/cache, @oshun/database, @oshun/event-bus, and
@oshun/queue as dependencies in package.json. In app.ts the storage
provider is pluggable via MotionPipelineServiceOptions.storageProvider; when
none is supplied a built-in mock provider is used.
12.2 Motion AI Service#
apps/aja/svc-motion-ai/src/server.ts reads PORT (default 3040).
12.3 CLI#
apps/aja/cli/src/index.ts reads AJA_API_URL (default
http://localhost:3000) and AJA_API_KEY, and loads a .env file via
dotenv.
12.4 SDK#
@aja/motion-pipeline-sdk takes all configuration through the SDKConfig
object passed to MotionPipelineClient; it does not itself read environment
variables. DEFAULT_SDK_CONFIG supplies the default timeout and retry policy.
13. CLI#
apps/aja/cli publishes the binaries aja and aja-motion (bin in
package.json), built with Commander. Source: apps/aja/cli/src/. The CLI
depends on @aja/motion-pipeline-sdk, @aja/motion-formats,
@aja/motion-quality, and @aja/motion-validation.
Global options (available on all subcommands): -v, --version, -q, --quiet,
--no-color, --json, --api-url <url>, --api-key <key>.
Seven subcommands are registered (commands/index.ts), each implemented in a
dedicated source file:
| Command | Source | Purpose |
|---|---|---|
aja process |
process.ts |
Process a video / mocap file |
aja convert |
convert.ts |
Convert animation files between formats |
aja inspect |
inspect.ts |
Inspect a motion file's metadata/quality |
aja debug |
debug.ts |
Run a pipeline stage with diagnostics |
aja config |
config.ts |
Manage named pipeline configuration profiles |
aja jobs |
jobs.ts |
List/monitor/manage submitted jobs |
aja health |
health.ts |
Check pipeline service health |
CLI utilities under cli/src/utils/ provide shared building blocks: client.ts
builds the service client, output.ts handles formatted output (including
--json mode), and progress.ts renders progress indicators.
14. Validation Rules and Invariants#
These rules define the correctness constraints that the Aja domain enforces at runtime. A change that violates any of these invariants is incorrect.
- Pipeline configuration validation — created configs are validated with
validatePipelineConfigurationbefore being saved; invalid configs return HTTP400 INVALID_CONFIGwith anerrorsarray. YAML configs additionally fail withYAML_PARSE_ERRORon a malformed body. - YAML schema —
parsePipelineDefinitionenforces thePipelineDefinitionshape (apiVersion,kind: 'PipelineDefinition',metadata,spec). Thespecquality preset must be one ofdraft,standard,high,ultra; job priority (when present) must be one of the fiveJobPriorityvalues. - Job lifecycle invariants — a job's outputs can be listed only when its
status is
completed(JOB_NOT_COMPLETEotherwise); an SSE stream can be opened only when status ispending,queued, orrunning(JOB_NOT_ACTIVEotherwise). - V2-deferred stages — if a pipeline config enables
pose-estimation,skeleton-fitting,domain-analysis, orretargeting, the processor marks the stage'skipped'(it does not fail the job) and logs theV1-P2-0331deferral. - Embodied-instruction request validation — the demonstration-plan,
coaching-overlay, and session-handoff endpoints reject malformed bodies with
HTTP
400and Zod-flattenedissues. - Webhook progress throttling — webhook
job.progressdeliveries fire only at progress percentages 25, 50, and 75. - SDK retry policy — the SDK retries only retryable errors on the configured retryable status codes, with exponential backoff plus jitter.
- Format scale conventions — C3D and TRC import default to
scaleFactor: 0.001(millimetres to metres); USD export defaults tometersPerUnit: 0.01(centimetres). - Skeleton parent invariant — in
SkeletonJoint,parentIndexof-1denotes the root joint;STANDARD_JOINT_NAMESandSKELETON_PARENT_INDICESarrays must be index-aligned per skeleton type — indexiin the names array must correspond to indexiin the parent-indices array.
15. Technology Stack#
The table below covers the key technology choices for each layer of the Aja domain.
| Layer | Technology |
|---|---|
| Language | TypeScript (Node.js); Python for the aja-motion-pipeline SDK |
| Pipeline service | Fastify, via @lilith/fastify-core (port 8090) |
| AI service | Hono + @hono/node-server (port 3040) |
| CLI | Commander, with chalk, inquirer, ora, dotenv, glob |
| Validation | Zod (AI service request validation; pipeline YAML/config) |
| Job progress delivery | Server-Sent Events (not WebSocket) |
| YAML | js-yaml (pipeline definitions) |
| Motion format I/O | Custom TypeScript parsers in @aja/motion-formats |
| Build | tsc per project (nx for the CLI build script) |
| Testing | Vitest (TypeScript); pytest tooling for the Python SDK |
| Shared infrastructure | @oshun/cache, @oshun/database, @oshun/event-bus, @oshun/queue, @oshun/config, @oshun/logging, @oshun/errors, @oshun/health (declared as deps) |
16. Acceptance Criteria#
A change to the Aja domain is acceptable when all of the following conditions hold:
- Type integrity — all motion data flows through
AnimationClip/SkeletonDefinitionfrom@aja/motion-formats; new skeleton types are added to the relevantStandardSkeletonTypeunion and toSTANDARD_JOINT_NAMES/SKELETON_PARENT_INDICESindex-aligned. - Pipeline-stage discipline — new pipeline stages are added to the
PipelineStageunion; ML-only stages remain inV2_DEFERRED_STAGESuntil a real implementation lands, and the processor skips (never fakes) deferred stages. - API consistency — pipeline routes stay under the
/v1/pipeline/...prefix and return the documented error-body shape{ code, message }; AI routes stay under/api/v1/.... - Validation — request bodies that have a Zod schema (the
embodied-instruction endpoints) or a config validator are rejected with HTTP
400and structured issues on malformed input. - Event coherence — job lifecycle changes emit the corresponding
JobEvent; webhook and SSE deliveries derive from those events; webhookjob.progressstays throttled to 25/50/75 %. - Build and test — every changed library and app type-checks
(
npx tsc --noEmit) and its Vitest suite passes; the Python SDK's tooling passes formotion-pipeline-sdk-python. - No fabricated capability — documentation and code claims are limited to
what exists under
libs/aja/*andapps/aja/*.