Glia: a policy card for every component.
A Glia card is inserted into an Axon, an Effector or an Engram. It reads every signal going into that component and every signal coming out of it, and refuses, redacts or escalates the ones its policies match. There is no orchestrator and no central policy process. A card is a local object, and it is never consulted over the wire.
The policy sits on the component, not on the bus.
A guard written into a component is a guard you rewrite for every component. A card is written once, issued to one component, and travels with it. Each component carries at most one, so there is one answer to the question of what protects it.
| Component | Inbound | Outbound |
|---|---|---|
| Axon | TASK | AGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR |
| Axon, its own calls | TOOL_RESULT, RECALLED, IMPRINTED | TOOL_CALL, RECALL, IMPRINT |
| Effector | TOOL_CALL | TOOL_RESULT |
| Engram | RECALL, IMPRINT | RECALLED, IMPRINTED |
The Dendrite routes and publishes, and has no policy role. A card on a Dendrite is read for trace-wide limits only, because those count a whole trace rather than one component.
Four fields. No checkpoints.
By default a policy reads every signal, both ways. Scope is something you narrow, not something you pick from a list of hook points.
direction inbound · outbound · both
Which way the signal is crossing this component. The default is both. The same IMPRINT is outbound for the Axon that sent it and inbound for the Engram that receives it, so each side can hold its own rule about it.
types signal types, or *
Which signals the policy reads. The default is all of them. A policy scoped to TOOL_CALL reads tool calls and nothing else, and a signal no policy selects costs one set lookup.
verdict allow · deny · redact · escalate
What happens on a match. Redact rewrites the payload on a copy and the run continues on the copy. Escalate raises PERMISSION. A handler sees a read-only view of the whole envelope, not only the payload, and cannot write to it.
retry reask · resend · none
What a deny does next. reask re-runs the Axon's Neuron with the refusal as the correction. resend repeats the request behind a TOOL_RESULT or RECALLED. none refuses now, and is the default. Each policy carries its own max_attempts.
A card is a versioned artifact first. It is parsed at load, reports its version on REGISTER, and an audit record names the policy version that fired. With twenty components, reading twenty cards tells you what is enforced.
{
"card_id": "agent-memory",
"version": "1",
"issued_to": "agent-memory",
"mode": "enforce",
"journal": {"path": "report/glia/memory.jsonl"},
"policies": [
{
"id": "no-credentials-in-memory",
"types": ["IMPRINT", "RECALLED"],
"verdict": "redact",
"match": ["sk-[A-Za-z0-9_-]{20,}", "AKIA[0-9A-Z]{16}"],
"replacement": "[credential removed]"
}
]
}What a declaration cannot say goes in a decorator on the card, so it still travels with the card rather than living in the component. Handlers run in order and the first one that returns a verdict answers.
from cosmonapse import Glia, Verdict CARD = Glia.load("glia/files.json") @CARD.on_signal(direction="inbound", types=["TOOL_CALL"]) def writes_stay_in_report(sig): if sig.payload.get("tool") != "write_file": return Verdict.deny("this Effector serves write_file only") path = (sig.payload.get("args") or {}).get("path") or "" if not (ROOT / path).resolve().is_relative_to(REPORT_DIR): return Verdict.deny("writes are confined to report/") return None # no opinion: the next policy decides
Axon(neuron_id="research", neuron_fn=research, glia=research_card) Effector.serve(effector_id="files", glia=files_card) Engram.serve(engram_id="agent-memory", glia=memory_card)
Off, then audit, then enforce.
The library default is off. The deployment chooses when a card starts reading and when it starts blocking.
Nothing is read
The default. A card in off mode binds no gate, so mounting one changes nothing. Every brain written before 0.1.14 runs exactly as it did.
Read and record
Policies run and record what they would have done. Nothing is blocked and nothing is retried. This is the shadow period, and turning on enforce without one is a guess.
Apply
Verdicts are applied. A policy handler that raises fails closed unless the card opts into fail_open. COSMONAPSE_POLICY_MODE overrides the card's mode for a whole deployment.
No AUDIT on a trace means nothing fired.
AUDIT is a new signal type on its own subject, so a compliance consumer can subscribe to cosmonapse.<ns>.AUDIT and nothing else. One record is emitted at each event, including the refusal that exhausts a retry budget. That is what makes the absence of a record mean something.
| Kind | Domain | Emitted when |
|---|---|---|
| GUARD_RETRY | security | A deny that was retried, or whose retries ran out |
| GUARDED | security | A refusal, redaction or escalation with no retry |
| EVAL_RETRY | eval | An output nothing claimed, sent back to the Neuron |
| TOOL_RETRY | tool | Tool arguments that failed their schema |
| TRANSIENT_RETRY | reliability | An Effector or Engram retrying a transient failure |
| DEADLINE_ABANDONED | reliability | A repair that ran past its deadline |
| LIMIT_REFUSED | governance | A trace limit on the Dendrite declined an action |
On the bus, thin
The AUDIT record and the reply's meta.glia carry the verdict, the policy id and version, the direction, the component and a hash of what matched. Never the matched text.
On disk, full
The matched content goes to the card's own append-only journal on the component's host. It never crosses the Synapse. A collector ships it to wherever compliance lives.
One loop for three kinds of wrong.
The Axon now has a correction loop. It re-asks the Neuron when a policy refuses with retry: "reask", when tool arguments fail their schema, and when an output matches nothing under the opt-in strict_output.
Bounded
A re-ask is bounded by the lower of the policy's max_attempts and the card's repair budget, and optionally by a deadline. An attempt whose input would be identical to the last one is not made.
Kept apart from transient retry
Effector and Engram transient retry is a separate setting. A policy refusal never feeds it, because resending an identical request to a deterministic policy is the same denial each time.
Refusals are typed
When retries run out, the component answers in its own reply type: an AGENT_OUTPUT with refused_by: "policy", a TOOL_RESULT or RECALLED with an error. A refusal does not become an ERROR.
The limits, stated plainly.
What a card can and cannot promise in 0.1.14.
Glia does not authenticate signals
directed.id is a claim. Until envelopes are signed, any participant on a broker can publish as any id, so a policy on identity stops mistakes and misrouted traffic, not an attacker. Treat a shared broker as a trusted zone. Signing is the next phase.
An empty card does nothing
Cosmonapse ships the seam, not a PII library or an injection detector. The regex lists in the examples show where such a check goes. They are not presented as one.
Code that holds the Dendrite can go around an Axon's gate
A Neuron that calls the Dendrite's publish methods directly is not read by its own Axon's card. The card on the receiving Effector or Engram still reads what arrives.
Older peers drop AUDIT
A peer on an SDK before 0.1.14 logs one warning and drops an AUDIT signal it does not know. The verdict also rides the reply as meta.glia, correlated by parent_id, so that copy is never lost.