11 KiB
External lifecycle capture
An orchestrator that observes its agents can send lifecycle events to one shared
ai-memory server through /hook/batch. Framework adapters belong outside core;
the server continues to own storage, retrieval, consolidation and durable memory.
For deliberate writes, queries and handoffs without lifecycle capture, start with the programmatic memory guide.
Choose one capture path before launching
Set AI_MEMORY_CAPTURE_OWNER to your producer namespace in the environment of
the harness you launch, for example example.runtime. Any non-whitespace value
opts that process and its inheriting children into external capture. Unset,
empty and whitespace-only values preserve normal native capture.
Orchestrator
|-- launches harness with AI_MEMORY_CAPTURE_OWNER=example.runtime
|-- records its lifecycle events with stable IDs
`-- sends those events through POST /hook/batch
Harness
|-- installed hooks keep delivering supported startup context
`-- MCP keeps providing recall and deliberate memory writes
Updated native hook commands, POSIX/PowerShell hook bundles, and generated
OpenCode/OpenCode 2/OMP/Pi/OpenClaw integrations honor the variable. Reapply
install-hooks after upgrading to refresh staged scripts or generated code.
Older installations do not understand this context.
| Responsibility | With external capture |
|---|---|
| Native lifecycle observations | Skipped before spool, queue or POST |
| Native CLI hook drains and SessionStart backfill | Skipped |
| Supported handoff, briefing and inbox-notice delivery | Preserved |
| Native identity used by handoff/session-aware MCP | Preserved |
| MCP recall and deliberate writes | Unchanged |
| Repository capture policy | The producer checks and applies it before queueing each event |
Claude Code receives context through SessionStart. Kimi Code receives it through
UserPromptSubmit because it discards SessionStart output. The same existing
delivery paths remain in use. Context delivery may register the receiving session
and claim a handoff; suppressing capture does not make it read-only. Harnesses
without automatic injection can still use memory_handoff_accept through MCP.
Do not also claim that handoff in the orchestrator if the hook owns delivery.
This is inherited process context, not server configuration, a security identity, or a lease. Keep it out of the shared server's environment and global shell configuration. Standalone launches without it continue to capture normally. A runtime hosting multiple agents in one process applies the variable to all its installed ai-memory integrations; it must manage finer capture ownership itself.
ai-memory hook --check-capture reports external_capture: true and
admits_capture: false when this context is active. It performs no ingestion or
handoff delivery. The producer namespace itself is not printed or authenticated.
Use policy_admits_capture for the external producer's preflight: it ignores
capture ownership but checks repository policy, event eligibility, exclusions,
scope completeness and server-profile resolution. Partial scope and a rejected
profile return false. Apply disposition, including metadata-only stripping,
before queueing. scope contains sanitized local routing hints of at most 512
bytes each; oversized hints are omitted and preflight fails closed.
scope_resolution distinguishes explicit, partial, server-derived and
unavailable scope. server_may_remap warns that the server may choose different
coordinates. Native routing values are unaffected by the inspection bound.
ai-memory doctor reports whether this invocation delegates native capture and
shows its authenticated machine identity. Its per-agent counts flag sessions
with multiple capture sources (native events or distinct extensions, including
backfill). An older server leaves those
fields unknown; a mixed count is a diagnostic signal, not proof of duplicates.
Reuse the public event contract
For a live harness, send its actual wire agent (such as claude-code or codex)
and native session_id. Use the same session ID, cwd, scope and authenticated
operator as its remaining native integration. The body's session ID takes
precedence over a query parameter. Replacing only the ingestion ID with an
orchestrator's logical agent ID separates it from native handoff/MCP identity.
Use explicit workspace and project for each event. Preserve canonical
lifecycle names (session-start, user-prompt-submit, post-tool-use,
session-end, etc.) and the supported harness payload shape. session-end
means the execution ended, not a turn completed. Sessions containing only
unknown events are not substantive sessions for automatic summaries.
Set extension=<producer-namespace> and an explicit source_event=<event-name>
for provenance. Both are needed to preserve the namespace on recognized events.
extension accepts up to 64 ASCII token characters; source_event accepts 128.
Letters, digits, ., _, - and : are allowed. These fields describe the
source; they do not expand the core event enum or retain arbitrary metadata.
Keep logical agents, parent/child relationships and workflow IDs in adapter-side
state for now. Do not repurpose native agent_id fields: some harness payloads
use them as subagent markers. The existing managed-workstream managed_run
field also has its own lease, ledger and handoff behavior.
Stable retry identity
ingest_key is an existing project-scoped key, independent of extension.
Construct it once from stable event identity and persist it with the event before
delivery. Never hash prompt/tool content to decide whether events are equal.
For example, an adapter using Node can derive a 64-character key as follows:
import { createHash } from "node:crypto";
function ingestKey({ producer, actor, agent, sessionId, sourceEvent, eventId }) {
const identity = JSON.stringify([
"external-capture-v1", producer, actor, agent,
sessionId, sourceEvent, eventId,
]);
return createHash("sha256").update(identity, "utf8").digest("hex");
}
Here actor is a stable adapter-side operator namespace, never a bearer token.
Include the coordinates within which eventId is unique. An adapter with
producer-wide unique event IDs may use a smaller tuple. Keep its encoding and
values stable across process restarts and implementation languages.
The same producer/event identity yields the same key on retry. Distinct event
IDs or producer namespaces yield distinct keys even for equal payloads. Reusing
the raw event ID as ingest_key under two extensions does not namespace it.
Keys accept 1 to 64 ASCII letters, digits, _ or -. Malformed keys fall back
to unkeyed ingestion, so validate them before sending.
The server claims the key and observation in one transaction. A completed replay is skipped; a pending replay resumes downstream work without inserting another observation. Keys expire after 30 days. This is bounded retry protection, not permanent deduplication or an exactly-once guarantee for every wiki side effect.
Ordered batches and recovery
A batch uses the existing array of {url, body} items:
[
{
"url": "/hook?event=user-prompt-submit&agent=claude-code&workspace=team&project=app&extension=example.runtime&source_event=user-prompt-submit&ingest_key=example-runtime-run-17-prompt-4",
"body": {
"session_id": "native-session-17",
"cwd": "/work/app",
"prompt": "Continue the parser fix."
}
}
]
Send this JSON to POST /hook/batch with the normal bearer authentication. The
URL in each item supplies the event query; the server does not fetch that URL.
The short key above assumes the runtime assigned a producer-wide unique run ID.
Use the tuple recipe when event IDs have narrower scope.
- At most 256 items fit in a batch. Existing body limits and sanitization apply.
- Batch processing happens inline, in array order. There is no global ordering across concurrent requests and no all-or-nothing transaction for a batch.
- Prefer
accepted_indiceswhen present. Otherwiseacceptedis the contiguous acknowledged prefix. Preserve every unacknowledged item for retry. resultscontains one{index, outcome}for each acknowledged index, in index order, including an empty array when nothing was acknowledged. Outcomes arestored,replayed,resumed,ignored_end,dropped_policy,dropped_subagent,dropped_unauthorizedanddropped_collision. A drop is a terminal acknowledgement. Older servers omitresults; the relay records those receipts asunknown.- A rate-limited source can be skipped while other sources advance. Inspect
acknowledgements even on HTTP 429 or a partial failure;
failed_indexidentifies a processing failure after earlier skips. An acknowledgement can also mean a deliberate policy drop, not a new observation. - On a timeout or lost response, retry unchanged items and keys. Back off on saturation rather than opening an unbounded number of requests.
Maintain a durable producer-side queue if offline/restart recovery matters. The optional lifecycle relay supplies one through a separate CLI. It accepts events from the orchestrator and sends only the first pending event of each session in a batch. Flush earlier events for a session before sending its terminal event. Serialize delivery within a session where order matters; different sessions can share the server concurrently. Late observations and a later terminal event use existing resumed-session behavior. Stopping a parent does not synthesize endings for its children; the orchestrator must report those lifecycle transitions.
POST /hook remains available, but its 202 response precedes persistence.
Prefer /hook/batch when the producer needs an inline processing acknowledgement.
Boundaries
The variable does not erase or cancel existing spool entries. Explicit
hook-drain and other standalone hooks can still deliver that backlog.
Generated integrations also retain their existing queue disposal behavior,
which can drain previously captured events. Choose the capture owner before
the execution's first event; switching midway cannot deduplicate events already
produced by independent paths.
Manual backfill, the importer, finalize-session and managed transcript import
through ai-memory run retain their existing behavior. Do not combine them as
independent lifecycle producers for the same execution. External capture does
not disable native harness transcript files or other vendors' hooks.
Auth, owner checks, admission rules and sanitization still apply. All items in
one batch share one authenticated identity; separate batches by operator when
representing multiple users. extension is self-declared provenance and the
capture variable grants no permissions. Project access modes and grants apply
to DB-user tokens. Pages are shared by authorized project members; session and
handoff ownership still applies. Producer namespaces provide provenance only.
See multi-user attribution.
No framework adapter, workflow engine, generic plugin runtime or OpenTelemetry collector is introduced. A future OpenTelemetry adapter can map conversation and tool-call identities at the boundary. Keep ingestion producer identity separate from the model provider and preserve the native session identity needed by ai-memory's existing integrations.