// The gate

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.

A synapse bus across the top. Below it, three participants: an Axon, an Effector and an Engram. Each sits inside its own dashed frame labelled glia card. Every arrow between the bus and a participant passes through that participant's frame, one arrow inbound and one outbound.Synapseglia cardinoutresearchAxonin: TASK · TOOL_RESULT · RECALLEDout: AGENT_OUTPUT · TOOL_CALL · RECALLglia cardinoutfilesEffectorin: TOOL_CALLout: TOOL_RESULTglia cardinoutmemoryEngramin: RECALL · IMPRINTout: RECALLED · IMPRINTEDa violation emits one AUDIT record, then the policy decides: re-ask, resend, or refuse
One card per component. It reads what arrives and what leaves, both ways, and the bus between components carries no policy of its own.
ComponentInboundOutbound
AxonTASKAGENT_OUTPUT, CLARIFICATION, PERMISSION, ERROR
Axon, its own callsTOOL_RESULT, RECALLED, IMPRINTEDTOOL_CALL, RECALL, IMPRINT
EffectorTOOL_CALLTOOL_RESULT
EngramRECALL, IMPRINTRECALLED, 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.

// A policy

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.

Declared

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.

glia/memory.json
{
  "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]"
    }
  ]
}
In code

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.

glia/files.py
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
Mounted
brain.py
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)
// Modes

Off, then audit, then enforce.

The library default is off. The deployment chooses when a card starts reading and when it starts blocking.

off

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.

audit

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.

enforce

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.

// Audit

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.

KindDomainEmitted when
GUARD_RETRYsecurityA deny that was retried, or whose retries ran out
GUARDEDsecurityA refusal, redaction or escalation with no retry
EVAL_RETRYevalAn output nothing claimed, sent back to the Neuron
TOOL_RETRYtoolTool arguments that failed their schema
TRANSIENT_RETRYreliabilityAn Effector or Engram retrying a transient failure
DEADLINE_ABANDONEDreliabilityA repair that ran past its deadline
LIMIT_REFUSEDgovernanceA 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.

// The repair loop

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.

// What Glia is not

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.

Back to Core →