Skip to content

Live Ingress

Live ingress is how a JacqOS app accepts real input without giving up replay. HTTP clients, chat sessions, webhooks, local scripts, and Studio all use the same contract:

append observations -> run the lineage -> subscribe to events

There is no separate chat runtime or webhook runtime. Adapters append observations, JacqOS derives facts and intents from the ontology, effect authority decides whether effects may execute, and the event stream publishes durable projections you can resume.

Run serve from a JacqOS app directory:

Terminal window
jacqos serve --port 8787 --json

The default bind address is loopback. Binding outside loopback requires both --allow-non-loopback and --auth-token-env; unauthenticated non-loopback serve is rejected. The JSON receipt includes the listen address, auth state, inspection metadata, and the retention stance for observations, run records, attempt reports, idempotency rows, and durable SSE events.

A live producer can use the generic endpoints directly:

POST /v1/lineages/live-demo/observations
POST /v1/lineages/live-demo/run
GET /v1/lineages/live-demo/events?since=head:0

POST /observations appends one immutable observation. POST /observation-batches appends ordered JSONL or an ordered array. Both surfaces accept idempotency fields so a retry can return the original append receipt instead of duplicating evidence.

POST /run evaluates one lineage. Use shadow to evaluate without effects, prefer_committed_activation to execute only when the loaded package matches the committed activation, and require_committed_activation when a mismatch must fail instead of falling back to shadow.

The event stream is Server-Sent Events:

GET /v1/lineages/live-demo/events?since_event=12
GET /v1/lineages/live-demo/events?since=head:42&relation=agent.alert
GET /v1/lineages/live-demo/events?event_type=reconciliation.required

Use Last-Event-ID or since_event to resume. Use relation when one subscriber owns one derived relation, and event_type when it needs a lifecycle event such as fact.delta, intent.admitted, effect.succeeded, reconciliation.required, or run.completed.

If the client falls behind a bounded catch-up request, JacqOS emits stream.backpressure and closes the stream. If a future retention policy ever removes the requested window, JacqOS emits stream.resume_window_exceeded and the client must recover from the query endpoints.

Live clients should catch up from query endpoints before opening or resuming a stream:

SurfaceEndpoint
Lineage statusGET /v1/lineages/{lineage_id}/status
Observation tailGET /v1/lineages/{lineage_id}/observations?from_head=N
Fact deltasGET /v1/lineages/{lineage_id}/facts?from_head=N&to_head=M
Intent deltasGET /v1/lineages/{lineage_id}/intents?from_head=N&to_head=M
EffectsGET /v1/lineages/{lineage_id}/effects
RunsGET /v1/lineages/{lineage_id}/runs
ProvenanceGET /v1/lineages/{lineage_id}/provenance?fact_id=...

Facts, intents, and effects are evaluator-scoped. If the request omits evaluator_digest, JacqOS uses the committed activation for the lineage, then falls back to the latest run. If neither exists, the query returns evaluator.unavailable instead of guessing.

The chat adapter is a thin wrapper over append, run, and subscribe:

POST /v1/adapters/chat/sessions/{session_id}/messages
{
"message_id": "msg-1",
"text": "Can you check my order?",
"once": true,
"effect_mode": "shadow"
}

The adapter writes chat.user_message to lineage chat:{session_id}. It auto-creates only that chat: lineage prefix, uses chat:{session_id}:{message_id} as the default idempotency key, runs the lineage, and returns:

  • the observation receipt,
  • the run_id,
  • an events_url filtered to accepted chat.assistant_message facts,
  • accepted assistant-message projections, each with a provenance_url.

Your ontology still decides what an assistant message means. A model or parser may produce candidate or proposal facts, but only a domain rule should derive the accepted chat.assistant_message relation that the adapter returns.

The webhook adapter is for non-chat producers that need signature validation and delivery idempotency before append:

POST /v1/adapters/webhooks/{adapter_id}/deliveries
{
"lineage_key": "account-42",
"delivery_id": "evt-1001",
"kind": "webhook.delivery",
"payload": {
"account_id": "account-42",
"event_type": "invoice.paid"
},
"signature": "sha256:...",
"secret_env": "JACQOS_WEBHOOK_SECRET",
"once": true,
"effect_mode": "shadow"
}

The adapter validates the signature before appending anything. The current V1 local signature shape is sha256(secret + "." + canonical_signed_payload), where the signed payload contains adapter_id, delivery_id, kind, and payload. If the signature fails, JacqOS returns webhook.signature_invalid and the observation log is unchanged.

Valid deliveries append to lineage webhook:{adapter_id}:{lineage_key}. The default idempotency key is webhook:{adapter_id}:{delivery_id}. The receipt returns the observation address, idempotency digest, run_id, events_url, and run receipt.

Studio can connect to serve instead of reading a stale local snapshot:

Terminal window
export JACQOS_STUDIO_SERVE_URL=http://127.0.0.1:8787
export JACQOS_STUDIO_LINEAGE=live-demo
jacqos-studio

If serve requires auth, also set:

Terminal window
export JACQOS_STUDIO_SERVE_TOKEN="$JACQOS_SERVE_TOKEN"

In serve mode, Studio reads the public query and event surfaces: lineage status, observation tail, fact and intent deltas, effects, run records, provenance, contradiction/invariant evidence from attempt reports, and reconciliation-required events. It does not introduce a second live truth path.

Use one lineage when independent producers need shared reality. Each producer appends observations in its own vocabulary. The ontology derives shared facts. Subscribers filter the event stream by the relation they own:

GET /v1/lineages/live-demo/events?since=head:0&relation=subscriber.risk_queue
GET /v1/lineages/live-demo/events?since=head:0&relation=subscriber.support_queue

The bundled Multi-Agent Live example demonstrates this pattern. A risk producer and support producer append independent observations. The ontology derives shared.review_required, emits relation-specific subscriber facts, and uses an observed dispatch receipt to retract the dispatch intent so the subscriber does not loop.

A live run is not special evidence. It is an observation history plus durable operational projections. To prove a live path, capture the same observation sequence as JSONL and replay it:

Terminal window
jacqos observe --jsonl fixtures/shared-reality.jsonl --lineage live-demo --create-lineage --json
jacqos run --lineage live-demo --once --shadow --json
jacqos replay fixtures/shared-reality.jsonl
jacqos verify

The replay proof should not depend on run_id values, SSE event ids, or local adapter process state. Those are operational handles. Observations, facts, intents, effects, provenance, and fixtures remain the contract you review.