Skip to content

Praxis mode 2/8: Build cortex-gateway and the Cortex ingestion endpoint #1719

Closed

Description

@huang195

Part of #1714 · phase A, delivery step 2 · Sections 1, 2 and 3 · design in #1717 (docs/superpowers/specs/2026-11-07-praxis-mode-design.md)

Goal

Make Praxis record what it sees, and make Cortex accept it. The endpoint lands with its producer.

Scope

  • cortex_observe, one filter that parses (with cortex_parse from Praxis mode 1/8: Add the Rust workspace and port the inference parser #1718) and then records (the module cortex_record) in each hook. Praxis runs response hooks in reverse chain order, so two filters would record before parsing. cortex_record writes request and response rows in Cortex's event schema, with the documented field meanings. Deferred rows through emit_deferred_record: client_canceled, upstream_error, 413 with proxy_error for a body limit (as direct mode), and gateway_refused for other Praxis refusals. access_log after it in the chain, because Praxis emits deferred records only then. It reads X-Cortex-Egress-Error from the egress listener into the error kind.
  • cortex_attribution: refuses a request without a valid token with 403, before anything is recorded or dialed, and cortex_record writes no row for it. With a valid token it accepts X-Cortex-Attribution. Before the upstream it removes only the internal hop headers (the seven wire.go headers, X-Cortex-Attribution, -Request-Id, -Probe), and keeps X-Cortex-Dest and X-Cortex-Token for the egress listener. An application's own X-Cortex-… headers go through.
  • The emitter: a queue bounded by 4096 lines and by 256 MiB, and one streaming POST /v1/ingest. Over the byte limit it drops captures before rows. It never holds up traffic. Each line has startedAt.
  • Captures as direct mode keeps them: a decoded response capture only for Content-Encoding: gzip (other responses as they came), and request headers as the gateway receives them, without the hop headers (a known difference from direct mode).
  • A capture only for inference rows. When a line with its capture would go over the 64 MiB line cap, the capture is dropped and the row stays.
  • The binary cortex-gateway (praxis/server) on praxis-proxy =0.8.0, registered with register_filters!.
  • POST /v1/ingest (Go), only when praxis.enabled and bind_loopback_only. It has:
    • a bearer token and the DELETE /v1/sessions guards.
    • erase marks, in praxis mode only: a line whose startedAt is before an erase of its session is dropped, and its tunnel join is released.
    • no response capture without the same request's capture.
    • line format v1.
    • captures only for inference rows, with credentials removed again.
    • settle at ingestion, with a producer's cost record removed.
    • At set when the row is appended, for agentop's page-order check.
  • Captures: credential removal ported from core/redact and tested on the same cases.

Known edge cases to handle or accept (from the PR #1717 review)

  • The erase mark is in memory, so a Cortex restart loses it.
  • The erase check and the append are not one atomic step. A full fix is a check under the store's lock.
  • A stream longer than the 10-minute mark can still record after an erase. Direct mode has the same window for a response in flight at an erase.
  • With At set at append, a naming request that arrives before an earlier pending request could change which rows are linked to the session.

Done when

  • The replay test: an NDJSON capture through /v1/ingest gives the same /v1/sessions and /v1/usage as the live run.
  • In direct mode /v1/ingest answers 404 and no token file exists.
  • A mid-body abort gives client_canceled, and a body-limit refusal gives 413 with proxy_error, as in direct mode (spec [V 5]).
  • The CPU and heap of both processes are measured on a long Claude Code session with subagents, against direct mode. A request line is about 7–12 MiB (Tracker: Move the laptop data plane to Praxis when the upstream gaps close #1714, "Performance").

Depends on

#1718 (the inference parser).

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Metadata

Metadata

Assignees

Labels

laptopThe local (laptop) tool: install, abctl, the numbers

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions