Back to the bank transfer that makes you identify yourself twice. In Mantle the fix is project memory — declared once at the root, readable and writable by every skill.
# memory.yml
authenticated:
type: bool
description: Whether the caller has proven their identity this session.
initial_value: false
customer_id:
type: text
description: Meridian customer id, set once identity is proven.
preferred_language:
type: text
description: Language the customer asked to be served in.
initial_value: en
These are facts about the session and the end user, not about one skill’s workflow. Is the caller verified. Who are they. What language do they want.
Writing it
The authentication tool writes with a bare name:
context.memory.set("customer_id", customer["customer_id"])
context.memory.set("authenticated", True)
The slice resolves a bare name against the active skill first, then the project. Reads may also use a qualified name:
customer_id = context.memory.get("project.customer_id")
The asymmetry is easy to trip over: project.customer_id is valid on get,
but on set it fails validation with undeclared_memory_write. Write bare,
read either way.
Reading it
Both of the other skills’ tools open the same way:
customer_id = context.memory.get("project.customer_id")
if not customer_id:
return ToolResult(llm_response={"ok": False, "error": "not_authenticated"})
That is the whole trick. authenticate ran, wrote the id, and every later skill
picks it up. The caller is asked once.
Put the guardrail in the tool, not the prose
It is tempting to write the check into the instructions:
If the caller is not verified, ask them to sign in first.
Resist it. The tool already knows, deterministically, and a tool cannot be talked out of its answer. So the instruction branches on the tool’s result instead:
`fetch_balance` reads the signed-in customer from project memory, so it is the
authority on whether the caller is verified. If it returns `not_authenticated`,
tell the caller you need to verify them first and let the Authenticate skill
run. Never state a balance in that case.
The model is deciding what to say. The tool is deciding what is true.
A trap: immutable does not mean write-once
Project memory has an immutable flag, and the name invites exactly the wrong
guess. It looks like it should mean “once set, frozen for the session” — which
is how you would want customer_id to behave.
It does not. It means the field can never be written at runtime at all; it is
for constants seeded by initial_value. Put it on customer_id and
authentication itself fails:
Memory field 'customer_id' (slot 'project.customer_id') is immutable
and cannot be overwritten.
The tool call is refused, the caller never signs in, and the only sign of it is a warning in the server log. What actually makes verification stick is simpler: project memory persists for the session, and no other skill overwrites it.
