Skip to content

Event hooks

Astrocyte emits events at key points in the memory lifecycle. Hooks allow external systems to react to these events - for audit logging, alerting, custom workflows, and enterprise integration.


EventWhen it firesPayload includes
on_retainAfter a successful retainbank_id, memory_id, content_length, tags, policy_actions
on_recallAfter a successful recallbank_id, query, result_count, top_score, latency_ms
on_reflectAfter a successful reflectbank_id, query, answer_length, source_count, fallback_used
on_forgetAfter memories are deletedbank_id, memory_ids, reason, compliance
on_pii_detectedWhen PII barrier detects sensitive contentbank_id, pii_type, action_taken (redact/reject/warn)
on_rate_limitedWhen a request is rate-limitedbank_id, operation, limit, retry_after
on_circuit_breaker_openWhen provider circuit breaker tripsprovider, failure_count, last_error
on_circuit_breaker_closeWhen provider recoversprovider, recovery_duration
on_dedup_detectedWhen signal quality detects a duplicatebank_id, similarity_score, action_taken
on_noisy_bankWhen analytics flags a noisy bankbank_id, signal, threshold, action_taken
on_ttl_expiryWhen TTL policy archives or deletes memoriesbank_id, memory_count, action (archive/delete)
on_consolidationWhen consolidation creates observationsbank_id, observation_count, source_fact_count
on_bank_createdWhen a new bank is createdbank_id, profile, created_by
on_bank_deletedWhen a bank is deletedbank_id, memory_count, deleted_by
on_legal_holdWhen legal hold is set or releasedbank_id, hold_id, action (set/released)
on_importWhen memories are importedbank_id, memory_count, source_provider
on_exportWhen memories are exportedbank_id, memory_count, destination_path

Hook payloads align with the MemoryExportSink event taxonomy (memory-export-sink.md, provider-spi.md §5). Today, the practical path to land Iceberg, Delta, Parquet, or warehouse SQL tables is usually a webhook (§3.1) or Python callable (§3.3) that forwards events to your ingestor. When the core loads memory_export_sinks: from config, sinks should receive the same logical events in-process (at-least-once). Hooks never replace Tier 1 retrieval—they duplicate metadata-oriented signals for export and BI only unless you explicitly opt into content in payloads (§6).


hooks:
on_retain:
- type: webhook
url: https://internal.company.com/audit/memory-created
method: POST
headers:
Authorization: "Bearer ${AUDIT_API_KEY}"
timeout_ms: 5000
async: true # Non-blocking (default)
on_pii_detected:
- type: webhook
url: https://hooks.slack.com/services/T00/B00/xxx
method: POST
body_template: |
{"text": "PII detected in bank {{bank_id}}: {{pii_type}}, action: {{action_taken}}"}
- type: log
level: warning
message: "PII detected: {{pii_type}} in bank {{bank_id}}"
on_circuit_breaker_open:
- type: webhook
url: https://events.pagerduty.com/v2/enqueue
method: POST
body_template: |
{"routing_key": "${PD_ROUTING_KEY}", "event_action": "trigger", "payload": {"summary": "Memory provider circuit breaker open: {{provider}}", "severity": "critical"}}
on_noisy_bank:
- type: log
level: warning
- type: webhook
url: https://hooks.slack.com/services/T00/B00/yyy

HTTP POST to an external URL.

- type: webhook
url: https://example.com/hook
method: POST # POST (default) | PUT
headers: {} # Custom headers
body_template: null # Jinja2 template; if null, sends full event JSON
timeout_ms: 5000
retry_count: 2
retry_delay_ms: 1000
async: true # true = non-blocking (default), false = blocking

Emit a structured log entry.

- type: log
level: info # debug | info | warning | error
message: "{{event_type}}: {{bank_id}}" # Optional template; default: full event JSON

For in-process hooks (not available in MCP server or the Rust implementation):

from astrocyte import Astrocyte, HookEvent
async def my_custom_hook(event: HookEvent) -> None:
if event.type == "on_pii_detected":
await alert_security_team(event.data)
brain = Astrocyte.from_config("astrocyte.yaml")
brain.register_hook("on_pii_detected", my_custom_hook)

All events share a common envelope:

@dataclass
class HookEvent:
event_id: str # Unique event ID (UUID)
type: str # Event type (e.g., "on_retain")
timestamp: datetime # When the event occurred
bank_id: str | None # Affected bank (if applicable)
data: dict[str, Any] # Event-specific payload
trace_id: str | None # OTel trace ID for correlation

Payloads must be portable (see implementation-language-strategy.md): only str, int, float, bool, None, list, dict. No callables or opaque Python objects in serialized event data.


Hooks fire asynchronously after the operation completes. The operation’s response is not delayed by hook execution. If a webhook fails, it is retried according to retry_count but does not affect the operation.

- type: webhook
url: https://compliance.company.com/audit
async: false # Block until webhook returns 2xx

When async: false, the operation waits for the hook to complete. If the hook fails after retries, the operation still succeeds but a warning is logged. Use sparingly - this adds latency.

  • Async hooks: failures are logged and emitted as OTel span events. No retry exhaustion alert by default (configure via on_hook_failure meta-hook if needed).
  • Sync hooks: failures are logged, operation proceeds. The response includes a hook_warnings field.
  • Hooks never cause operations to fail. A failed webhook does not roll back a retain or reject a recall.

  • Webhook URLs are validated at config load time (must be HTTPS in production, HTTP allowed in development).
  • body_template uses sandboxed Jinja2 rendering (no file access, no code execution).
  • Secrets in URLs or headers use environment variable substitution (${VAR_NAME}), never stored in plaintext.
  • Hook payloads never include memory content by default. Only metadata (bank_id, memory_id, tags, scores). Content inclusion must be explicitly opted in:
- type: webhook
url: https://audit.company.com/full
include_content: true # Opt-in: include memory text in payload

Some hooks are always active (not configurable):

HookAlways firesDestination
All eventsOTel span eventsOpenTelemetry (if enabled)
Lifecycle eventsAudit logAudit trail (if enabled, see memory-lifecycle.md)
Prometheus countersMetricsPrometheus (if enabled)

User-configured hooks add to these built-in hooks, they don’t replace them.