---
icon: material/puzzle-outline
---

Extension runs the 10x Engine as its own [Lambda extension](https://docs.aws.amazon.com/lambda/latest/dg/lambda-extensions.html){target="\_blank"} inside each function's execution environment, paired over loopback with the [OTel collector extension](https://github.com/open-telemetry/opentelemetry-lambda){target="\_blank"} the function already runs. The collector hands every log record to the engine and receives it back regulated, routed, and hashed. No code changes, no new infrastructure, and nothing leaves the sandbox.

:material-ice-cream: This launcher employs the [Runtime flavor](https://doc.log10x.com/engine/flavors/#runtime) (native binary).

***Use-case***:

Per-pattern regulation on serverless estates. A 100% Lambda shop has no node, no DaemonSet, and no forwarder host to install onto; the execution environment itself is the edge. The extension applies the same receive pipeline the [Receiver](https://doc.log10x.com/apps/receiver/) runs on Kubernetes: tag noisy patterns for cheaper storage tiers, offload slices to your own S3, and mute what nobody reads, before the first byte leaves the function.

## :material-puzzle-outline: Benefits

### :material-layers-outline: A Layer, Not a Rebuild

The engine ships as a single Lambda layer: the native bootstrap under `extensions/`, with modules, config, and the symbol library under `/opt/tenx`. Attaching it to a function is a layer ARN plus environment variables; removing it is removing them. The layer unpacks to ~180 MB of Lambda's 250 MB shared layer budget.

### :material-target: Per-Pattern Control

Dispositions are keyed by pattern identity (`message_pattern`), not by function. A mute file decides each pattern's rate, with an explicit expiry and reason per entry, and a severity retention floor keeps ERROR/FATAL traffic flowing even under a full mute.

### :material-cloud-sync-outline: Policy Without Redeploys

`TENX_RECEIVE_MUTE_S3_URI` points the extension at a mute file in your own bucket. It fetches at INIT and refreshes on invoke (default 5 minutes), so one `aws s3 cp` converges every function. A failed fetch keeps the last good copy; dispositions degrade to the previous state, never to none.

### :material-lock-outline: Nothing Leaves the Sandbox

The collector pairing is loopback-only, and `TENX_AIRGAPPED=true` silences all engine egress to log10x. The only network path the extension adds is to your own S3 bucket, and only when you configure one.

## :material-hexagon-multiple-outline: Architecture Flow

<div style="text-align: center;">

```mermaid
graph LR
    A["Function<br/>Code"] -->|"Logs"| B["OTel Collector<br/>Extension"]
    B -->|"1. OTLP<br/>127.0.0.1:4317"| C["10x Engine<br/>Extension"]
    C -->|"2. Regulated events<br/>127.0.0.1:24225"| B
    B -->|"3. routeState<br/>routing"| D["Destination<br/>(SIEM)"]
    B -->|"offload slice"| E["Your S3<br/>Bucket"]

    classDef aws fill:#ff9900,stroke:#cc7a00,color:#ffffff,stroke-width:2px,rx:8,ry:8
    classDef tenx fill:#059669,stroke:#047857,color:#ffffff,stroke-width:2px,rx:8,ry:8
    classDef forwarder fill:#3b82f6,stroke:#1d4ed8,color:#ffffff,stroke-width:2px,rx:8,ry:8
    classDef destination fill:#6b7280,stroke:#4b5563,color:#ffffff,stroke-width:2px,rx:8,ry:8

    class A aws
    class B forwarder
    class C tenx
    class D destination
    class E aws
```

</div>

<div class="diagram-controls">
    <button class="md-button md-button--primary enlarge-diagram"
            onclick="enlargeDiagram(this)"
            data-diagram="extension"
            data-tooltip="Click to enlarge diagram">
        <span class="twemoji">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16">
                <path d="M10 2c4.42 0 8 3.58 8 8 0 1.85-.63 3.55-1.69 4.9L20.59 19l-1.41 1.41-4.09-4.09A7.84 7.84 0 0 1 10 18c-4.42 0-8-3.58-8-8s3.58-8 8-8m0 2a6 6 0 1 0 0 12 6 6 0 0 0 0-12m1 3h2v2h-2V7m-4 0h2v2H7V7m2 4h2v2H9v-2Z"/>
            </svg>
        </span>
        Enlarge Diagram
    </button>
</div>
<!-- Mermaid enhanced diagram functionality loaded via external files -->

**1. The function logs** as it always has. The collector extension picks events up through its existing receivers.

**2. The collector hands each record** to the engine over loopback OTLP/gRPC (`127.0.0.1:4317`) and receives it back on `127.0.0.1:24225`, now carrying `tenx_hash` (stable pattern identity) and `routeState` (the pattern's disposition) as log attributes.

**3. A routing connector fans out** on `routeState`: the SIEM slice ships through the existing exporter unchanged, the offload slice lands in your S3 bucket in the layout the [Retriever](https://doc.log10x.com/apps/retriever/) indexes, and drops end at the sandbox.

## :material-rocket-launch: Deployment

The [log10x MCP](https://doc.log10x.com/apps/mcp/) emits this entire plan from a discovery snapshot ([Advise](https://doc.log10x.com/apps/mcp/tools/install/advise-install/) returns it directly on a serverless estate, including a CDK construct). The steps below are the manual path.

??? tenx-bootstrap "Step 1: Prerequisites"

    | Requirement | Description |
    |-------------|-------------|
    | Log10x License | A license JWT ([get one](https://doc.log10x.com/run/bootstrap/#apikey)), passed as `TENX_LICENSE_KEY`. `TENX_AIRGAPPED=true` is required here, and is honored for every license type |
    | AWS CLI | [AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html){target="\_blank"} configured with Lambda and S3 permissions |
    | OTel Collector Extension | The target functions already run a collector extension with `routing` and `decouple` components (the community Lambda collector layer has both; a minimal custom build may not) |
    | S3 Bucket | Optional. One bucket serves both the offload slice and live mute-file delivery |

??? tenx-cloud "Step 2: Pick the Layer ARN"

    The layer is published in every region below, for both architectures, and
    is readable by any AWS account. Nothing to build.

    **Engine 1.1.79, republished 2026-09-17.** Every layer below carries the
    same build, so there is one digest per architecture rather than one per
    region:

    | Architecture | `CodeSha256` |
    |---|---|
    | x86_64 | `ACkSX/XS5nei+rAm6eiKyyxabq3lwcAqvAeFu437dDo=` |
    | arm64 | `DbhgTftIcnzEi+SenwH+YMotPX11sIavGQb6o17IBkU=` |

    `aws lambda get-layer-version` returns that digest to any caller, so you can
    check what you are about to attach:

    ``` { .console .copy }
    aws lambda get-layer-version --region us-east-1 \
      --layer-name arn:aws:lambda:us-east-1:351939435334:layer:tenx-receive \
      --version-number 8 --query Content.CodeSha256 --output text
    ```


    | Region | x86_64 | arm64 |
    |---|---|---|
    | `us-east-1` | `arn:aws:lambda:us-east-1:351939435334:layer:tenx-receive:8` | `arn:aws:lambda:us-east-1:351939435334:layer:tenx-receive-arm64:4` |
    | `us-east-2` | `arn:aws:lambda:us-east-2:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:us-east-2:351939435334:layer:tenx-receive-arm64:4` |
    | `us-west-2` | `arn:aws:lambda:us-west-2:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:us-west-2:351939435334:layer:tenx-receive-arm64:4` |
    | `eu-west-1` | `arn:aws:lambda:eu-west-1:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:eu-west-1:351939435334:layer:tenx-receive-arm64:4` |
    | `eu-central-1` | `arn:aws:lambda:eu-central-1:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:eu-central-1:351939435334:layer:tenx-receive-arm64:4` |
    | `ap-southeast-1` | `arn:aws:lambda:ap-southeast-1:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:ap-southeast-1:351939435334:layer:tenx-receive-arm64:4` |
    | `ap-northeast-1` | `arn:aws:lambda:ap-northeast-1:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:ap-northeast-1:351939435334:layer:tenx-receive-arm64:4` |
    | `ap-south-1` | `arn:aws:lambda:ap-south-1:351939435334:layer:tenx-receive:4` | `arn:aws:lambda:ap-south-1:351939435334:layer:tenx-receive-arm64:4` |

    Match the architecture to the function's own: `arm64` for Graviton,
    `x86_64` otherwise. A mismatch fails at INIT rather than silently.

    The layer carries the engine, modules, config, and symbol library. It
    carries no license, so the license travels as an environment variable in
    the next step.

??? tenx-config "Step 3: Attach the Layer and Environment"

    Write the environment as a JSON file. `TENX_RECEIVE_APPS` contains commas, which the AWS CLI's `--environment Variables={...}` shorthand silently mis-parses; `file://` is the reliable form.

    ``` { .json title="env.json" }
    {
      "Variables": {
        "TENX_MODULES": "/opt/tenx/modules",
        "TENX_CONFIG": "/opt/tenx/config",
        "TENX_SYMBOLS_PATH": "/opt/tenx/symbols",
        "TENX_LICENSE_KEY": "<your license JWT>",
        "TENX_AIRGAPPED": "true",
        "TENX_LOG_PATH": "/tmp/tenx/",
        "outputOffload": "true",
        "symbolMessageHashField": "tenx_hash",
        "log10xMetricsEnabled": "false"
      }
    }
    ```

    ``` { .console .copy }
    aws lambda update-function-configuration --function-name <fn> \
      --layers <tenx-receive-layer-arn> <otel-collector-layer-arn> \
      --environment file://env.json
    ```

    `TENX_LOG_PATH` matters: Lambda's filesystem is read-only outside `/tmp`, and a log appender that cannot create its directory poisons the pipeline launch.

??? tenx-forwarderinputs "Step 4: Splice the Collector Config"

    Merge the loopback pairing into the collector config the functions already run. Existing receivers, processors, and the destination exporter stay untouched:

    ```yaml
    receivers:
      # Return path from the engine extension (loopback, same sandbox)
      otlp/tenx:
        protocols:
          grpc:
            endpoint: 127.0.0.1:24225

    exporters:
      # Hand-off to the engine extension
      otlp/tenx:
        endpoint: 127.0.0.1:4317
        tls:
          insecure: true
        # The collector's first dial happens before the engine listens, and
        # default exponential retry intervals stretch across freeze until
        # exports fail forever. Bounded intervals keep retries inside a
        # thaw window.
        retry_on_failure:
          enabled: true
          initial_interval: 200ms
          max_interval: 1s
          max_elapsed_time: 0s

    connectors:
      routing/tenx:
        default_pipelines: [logs/tenx-siem]
        table:
          # context: log is required -- routeState is a LOG attribute; the
          # resource context never matches it.
          - context: log
            condition: attributes["routeState"] == "offload"
            pipelines: [logs/tenx-offload]
          - context: log
            condition: attributes["routeState"] == "drop"
            pipelines: [logs/tenx-drop]
    ```

    The full config, including the offload pipeline, the `decouple` processor placement, and the destination-side storage-tier policy, is emitted per estate by [Advise](https://doc.log10x.com/apps/mcp/tools/install/advise-install/).

??? tenx-mainconfig "Step 5: Per-Pattern Dispositions (Optional)"

    Without a mute file every lever is estate-wide. With one, each pattern gets its own disposition. Entries are `<pattern>,<rate>:<untilEpochSec>:<reason>`; rate `0` is a full mute, `0.25` keeps 25%, and past the epoch the entry expires on its own. The first line is a header row the engine's lookup consumes as column names:

    ```
    pattern,disposition
    heartbeat_check_ok,0:4102444800:liveness spam OPS-4821
    jwt_validated,0.25:4102444800:auth flood after deploy
    ```

    `4102444800` is 2100-01-01, a placeholder — set a real expiry. A past
    `untilEpochSec` is not an error: the file loads, the log says so, and
    the entry mutes nothing.

    Upload it to your bucket and add three variables to `env.json`:

    ```
    TENX_RECEIVE_APPS=@run/input/forwarder/otel-collector,@apps/receiver,@run/receive/rate
    rateReceiverFieldNames=message_pattern
    rateReceiverLookupFile=/tmp/tenx/mutes.csv
    TENX_RECEIVE_MUTE_S3_URI=s3://<your-bucket>/<prefix>/pipelines/run/receive/rate/mutes.csv
    ```

    The extension fetches the file at INIT and refreshes on invoke (`TENX_RECEIVE_MUTE_REFRESH_MS`, default 5 minutes), so changing a disposition is one `aws s3 cp` with no redeploy. The function role needs `s3:GetObject` on that key. Alternatively, bake the file into the layer with `build-receive-layer.sh`'s sixth argument; the policy is then static and a change means republishing the layer.

    For a recurring loop that recomputes dispositions from cost data on a schedule, [Setup recurring](https://doc.log10x.com/apps/mcp/tools/install/setup-recurring/) with `scheduler: "eventbridge"` emits an EventBridge Scheduler + CodeBuild stack that writes this same S3 key.

??? tenx-monitoring "Step 6: Verify"

    The extension logs its lifecycle to the function's log group:

    ``` { .console .copy }
    aws logs filter-log-events --log-group-name /aws/lambda/<fn> \
      --filter-pattern "tenx-receive" --max-items 10
    ```

    At the destination, processed records carry `tenx_hash` and `routeState` as log attributes; a muted pattern shows `routeState` of `drop` on all but its retention floor. If dispositions have no effect, check that the mute file's first line is the header row and that `rateReceiverFieldNames` is `message_pattern`.

??? tenx-advanced "Build the layer yourself (optional)"

    The published layer suits every case except one: baking a static mute file into the image, which pins the policy to the layer version. Build it from the release artifacts:

    ``` { .console .copy }
    gh release download 1.1.79 -R log-10x/pipeline-releases \
      -p 'tenx-edge-1.1.79-amd64-native' -p 'tenx-modules-1.1.79.tar.gz' \
      -p 'tenx-config-1.1.79.tar.gz' -p 'tenx-symbols-1.1.79.10x.tar' \
      -p 'build-receive-layer.sh'

    mkdir modules config symbols
    tar xzf tenx-modules-1.1.79.tar.gz -C modules
    tar xzf tenx-config-1.1.79.tar.gz -C config
    cp tenx-symbols-1.1.79.10x.tar symbols/

    chmod +x build-receive-layer.sh
    ./build-receive-layer.sh \
      ./tenx-edge-1.1.79-amd64-native ./modules ./config ./symbols \
      ./tenx-receive.zip \
      ./mutes.csv                  # the 6th argument is the policy file

    # The zip is ~90 MB; direct upload caps at ~70 MB, so stage through S3
    aws s3 cp tenx-receive.zip s3://<your-bucket>/layers/tenx-receive.zip
    aws lambda publish-layer-version --layer-name tenx-receive \
      --compatible-architectures x86_64 \
      --content S3Bucket=<your-bucket>,S3Key=layers/tenx-receive.zip
    ```

    Swap the bootstrap for `tenx-edge-1.1.79-aarch64-native` and the architecture for `arm64` to build the Graviton variant. With a mute file present the build validates every entry and refuses a malformed one, rather than shipping a disposition that would be silently skipped.
