An ordinary MOP often defines several operations on objects. Examples are a message-sending operation (send), a delegation operation (delegate), a cloning operation(clone),etc. Some language even have operations to add slots, delete slots and operations to access the internal state of an object. So, the following MOP gives a very realistic sketch of the MOP's in current day prototype-based languages:
object = { private: ... public: delegate(m:messagename,arguments:objects,self:object); addSlot(m:messagename, slot:expression); deleteSlot(m:messagename,slot:expression); readVariable(m:messagename); writeVariable(m:messagename,value:Object); send(m:message,arguments:objects) }
So when 'going meta', the above operations are the ones a programmer is allowed to perform on objects. The problem is that it is very difficult to trace (perhaps with extensive data-flow analysis) which code is meta and which is not. This is even more difficult in a dynamically typed language. Hence, whenever programmers are not satisfied with the protocol of the objects they get (for example from a library), all they have to do is go meta and breach the encapsulation of the object under consideration. Therefore, the entire idea behind Agora was to design a language in which only a single operation on objects is permitted: the one for sending a message (i.e. send).
object = { private: ... public: send(m:message,arguments:objects) }
Notice that once the send message is performed, the object knows all about itself (i.e. the private information above). It can use this knowledge to extend itself or clone itself. This is accomplished by mixin-methods and cloning methods. If the object contains the right implementation for it, it can even decide to export its own implementation details, but this can only be asked to the object by sending it a message. This is at the heart of the safety of Agora.