Rules
The same three axes as a memory, and the opposite treatment. A memory is distilled and weighed; a rule is written by a person and sent verbatim, at the top, worded as an order.
tenant whose it is: the firm
memorable what it is about, or NULL for the tenant as a whole
memory who it applies to. NULL = everyone; set = one person's preference
Plus two columns of its own:
Rule::create([ 'tenant_type' => 'organization', 'tenant_id' => $org->id, 'content' => 'Never quote a fee over chat. Refer them to the office.', 'priority' => 0, 'enabled' => true, ]);
priority is ascending, so 0 renders first, and ties break by id: two rules written the same day keep the order they were written in. enabled exists so a firm can stop a rule for a week and put it back without retyping it.
Rendering them
Rule::promptBlock($session);
Shared rules first, personal ones after, never interleaved. Flatten the two together and the model loses the only signal that tells a firm's policy from one person's preference, and starts trading one off against the other.
Within each, the wider scope before the narrower one, so a rule about this case reads as a refinement of the firm's rule rather than a competing instruction.
And the whole block goes above your own system prompt:
$parts = [ Rule::promptBlock($session), $yourPrompt, Memory::promptBlock($session), ];
A directive buried under a wall of remembered context stops reading as a directive. That ordering is the one thing in this chapter worth copying exactly.
The wording is yours
The defaults are deliberately blunt, because a rule the model treats as a suggestion is not a rule. They are also in English and generic, and yours are neither:
'rules' => [ 'headings' => [ 'everyone' => 'NORMAS OBLIGATORIAS DE ESTE :scope (no las omitas bajo ningún concepto):', 'actor' => 'PREFERENCIAS DEL USUARIO ACTUAL EN ESTE :scope (respétalas salvo que contradigan las normas obligatorias):', ], ],
:scope is replaced with the morph alias, so a firm reads as "this organization" or "this despacho" depending on what you called it. When one noun swapped into one template is not enough, override per scope:
'headings' => [ 'everyone.matter' => 'STANDING INSTRUCTIONS ON THIS MATTER:', 'everyone.firm' => 'FIRM POLICY. Applies to every matter without exception:', ],
What they are for
Rules are the answer to a question that comes up in every product with an assistant in it: where does "our firm always does X" live?
Not in the system prompt, because that is one string for everybody and this varies per tenant. Not in memory, because memory is distilled and weighed and might not surface. Not in the tools, because it is not an action. It is a standing instruction, written by an administrator, that has to be in front of the model every single time, and it needs a table.
The personal half is the same idea one level down. "Answer me in bullet points" is a real preference, it belongs to one person, and it must not become everyone's, which is exactly what happens the day somebody stores it in the tenant's system prompt.