Skip to content

Compile

Compile source code and binaries into a symbol library: a per-file .10x.json unit for each source file, plus a single linked .10x.tar that the 10x runtime later uses to assign hidden classes (TenXTemplates) to log events.

The call is bounded-synchronous. It waits inline up to max_wait_ms (default 45s), so a small compile, and a re-run over unchanged local sources, finishes inside the wait and returns the finished library plus the full scan and link diagnostics in one call.

A long first compile of a large tree overruns the wait and returns a running job_id to poll with Status. The run is detached and writes to a pinned output folder. Calling Compile again with the same arguments while it runs joins that job (same job_id) rather than starting a second engine on the same folder.

Set max_wait_ms: 0 to return the job_id immediately for fire-and-forget.

Sources combine freely in one call:

Docker pulls are daemonless. GitHub and Artifactory pulls each need a token, even for public GitHub repos.

The Compiler app runs the Compiler flavor, docker-first via the log10x/compiler-10x:1.1.136 image (pinned by digest) or a local Compiler-flavor tenx. It runs without Kubernetes, a deployed app, or a Log10x account.

What gets scanned, per the shipped scanner config:

  • parsed with a grammar or AST: Java, Scala, Python, Go, C/C++, C#, JavaScript, TypeScript (.ts; .tsx goes through the text scanner), Rust, and JVM .class bytecode
  • quoted-string extraction only, through the pattern scanner: Kotlin, Ruby, PHP, Lua, Groovy, Swift, bash
  • archives expanded and their entries scanned: .jar, .war, .ear, .zip, .gz, .tar and compressed tarballs
  • config and text through the text scanner: .json, .yaml, .xml, .properties, .csv, .txt and similar
  • native binaries through strings: .so, .dylib, and .dll on Windows hosts

Re-runs

The output folder is keyed on the sources, the library name, and the compiler (the image ref, or the local engine version), so units are reused only by the compiler that wrote them.

What a re-run skips: the engine skips every local file whose checksum already has a unit in the folder. Pulled sources cost a remote round-trip each run (GitHub branch head, image manifest digest, Artifactory listing) and are downloaded again only when the remote changed; Helm charts are rendered again every run. Only the scan of unchanged files is skipped.

The engine's own whole-scan cap (scanOperationTimeout, bundled default 10 minutes) is raised to 90% of timeout_ms on the command line, so the engine cap follows timeout_ms. That needs compiler 1.1.89 or newer; the pinned default is 1.1.136, and an older local engine keeps its 10 minute cap. A run the engine stopped early is reported as incomplete by Status.

Example

You

build a symbol library from ./payments-svc and the grafana image

Log10x

Compile done in 38s: 214 units → /tmp/log10x-mcp-compile/symbols-9f2c1a/symbols/symbols.10x.tar (4.2 MB). 3 files failed to scan (go 2, python 1). Symbols by type: class 1,840, log 612, exec 410, enum 96.

You

run it again, nothing changed

Log10x

Same sources and compiler, so the pinned output is reused: every local file skipped by checksum, the grafana image digest unchanged. Done in 4s.

More to ask

  • "compile apache/commons-cli from GitHub"
  • "scan this Helm chart and the images it references"
  • "kick off a compile of the whole monorepo and hand me the job_id"

Prerequisites

The Compiler flavor needs one of two things: Docker running so the log10x/compiler-10x image can be pulled (the default), or a local Compiler-flavor tenx with mode: "local". The Runtime flavor is refused.

GitHub pull (github_repos) needs a token even for public repos, passed as github_token or set as GH_TOKEN in the MCP server environment. Artifactory pull needs artifactory_token or ARTIFACTORY_TOKEN.

Archives are scanned as-is: .jar, .war, .ear, .zip and tarballs are expanded by the archive scanner and their entries scanned.

Schema and samples

Input example

A local folder plus a public docker image, with the inline wait left at the 45s default.

{
  "source_path": "/home/dev/payments-svc",
  "docker_images": ["docker.io/grafana/grafana:11.1.0"],
  "library_name": "payments",
  "max_wait_ms": 45000
}
Input schema

Agent-facing JSON Schema (the canonical shape the MCP server publishes via tools/list). At least one source is required; the sources combine freely.

{
  "type": "object",
  "properties": {
    "source_path": {
      "type": "string",
      "description": "Absolute path to a local folder of source code / binaries to scan, traversed recursively. Parsed with a grammar or AST: Java, Scala, Python, Go, C/C++, C#, JavaScript, TypeScript (.ts; .tsx goes through the text scanner), Rust, and JVM .class bytecode. Other languages in the pattern scanner's file list (Kotlin, Ruby, PHP, Lua, Groovy, Swift, bash) get quoted-string extraction only. Archives (.jar, .war, .ear, .zip, .gz, .tar and compressed tarballs) are expanded and their entries scanned; .json/.yaml/.xml/.properties/.csv/.txt and similar config/text files go through the text scanner; .so/.dylib (and .dll on Windows hosts) through `strings`. Optional when any other source is given; at least one source (source_path, github_repos, docker_images, helm_charts, or artifactory_instance + artifactory_repo) is required."
    },
    "github_repos": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "GitHub repositories to pull (via the GitHub REST API) and scan, each as owner/repo (e.g. [\"apache/commons-cli\"]). REQUIRES a GitHub token, even for public repos: pass github_token, or have GH_TOKEN / GITHUB_TOKEN set in the MCP server environment. Combines freely with source_path."
    },
    "github_branch": {
      "type": "string",
      "description": "Branch to pull for ALL github_repos. Omit to pull each repo's default branch."
    },
    "github_folders": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Folders within each GitHub repo to pull (e.g. [\"src/main/java\"]). Omit to pull entire repos. Narrowing this speeds up both the pull and the scan."
    },
    "github_token": {
      "type": "string",
      "description": "GitHub access token for github_repos (a fine-grained token with read-only Contents access to the target repos suffices). Falls back to GH_TOKEN / GITHUB_TOKEN from the MCP server environment. Reaches the compiler as process environment only; never written to disk or argv. When docker_images are given too, this same token additionally lets the compiler pull + scan each image's source repo (the org.opencontainers.image.source annotation); without it that extra scan is skipped silently."
    },
    "docker_images": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Docker/OCI images to pull and scan for symbols, as image refs, fully-qualified recommended (e.g. [\"docker.io/grafana/grafana:11.1.0\"]; a port-bearing host like \"harbor.corp:8443/team/app:2.1\" is fine, and a bare \"alpine\" resolves against the engine default registry). The pull is daemonless (podman inside the compiler-10x image, no host docker socket), and the tool automatically grants the compile container `--cap-add SYS_ADMIN` (needed by podman; only when this arg is used). Public images need no credentials. docker_username + docker_token `docker login` to the DEFAULT registry (Docker Hub), so they cover private Docker Hub repos; images on a different private registry must be pre-authenticated on the host/engine. In mode=local the host needs a docker/podman CLI with a working engine, and pulled images are left in its store (remove:false). Combines freely with source_path and github_repos."
    },
    "docker_username": {
      "type": "string",
      "description": "Registry username for docker_images login, also used for private images a Helm chart references (helm_pull_images). Authenticates the default registry (Docker Hub). Falls back to DOCKER_USERNAME from the MCP server environment. Omit for public images. The engine logs in only when BOTH username and token are non-blank. Reaches the compiler as process environment only."
    },
    "docker_token": {
      "type": "string",
      "description": "Registry token/password for docker_images (fed to `docker login --password-stdin` against the default registry, Docker Hub). Falls back to DOCKER_TOKEN from the MCP server environment. Omit for public images; pair with docker_username. Reaches the compiler as process environment only."
    },
    "helm_charts": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Helm charts to render and scan, as chart refs. A meta-source: the compiler runs `helm template` / `helm show chart` to extract the docker images and GitHub source repos the chart references, then (by default) pulls those too. OCI refs (e.g. \"oci://ghcr.io/nginxinc/charts/nginx-ingress\") and full URLs resolve standalone; a bare \"repo/chart\" (e.g. \"ingress-nginx/ingress-nginx\") needs a matching helm_repos entry. Combines freely with the other sources. In mode=local the host needs the helm CLI and must have the repos already `helm repo add`-ed."
    },
    "helm_repos": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Helm chart repositories to register before resolving helm_charts, each as \"name=url\" (e.g. [\"ingress-nginx=https://kubernetes.github.io/ingress-nginx\"]). Required for bare \"repo/chart\" names; unnecessary for OCI/URL refs. Added via `helm repo add` in pre-step containers (docker mode only; in mode=local the host helm config is used as-is). url must be an http(s):// chart-repo index URL; for an OCI registry, put the oci://… ref directly in helm_charts (`helm repo add` does not support oci://)."
    },
    "helm_pull_images": {
      "type": "boolean",
      "default": true,
      "description": "Whether to pull + scan the docker images a chart references (the richest symbol source for a chart). Default true. When true the tool grants the compile container `--cap-add SYS_ADMIN` (daemonless podman), same as docker_images. Set false to scan only the chart template/values text."
    },
    "helm_pull_repos": {
      "type": "boolean",
      "default": false,
      "description": "Whether to pull + scan the GitHub source repos a chart references (via org.opencontainers.image.source annotations). Default false because it REQUIRES a GitHub token (engine refuses an empty token); enabling it without github_token / GH_TOKEN returns not_configured."
    },
    "artifactory_instance": {
      "type": "string",
      "description": "Base URL of an Artifactory instance to pull artifacts (Java archives, .NET assemblies, etc.) from and scan, e.g. \"https://demo.jfrog.io/artifactory\". Requires artifactory_repo and a token (artifactory_token or ARTIFACTORY_TOKEN). Pull is via the Artifactory REST API, no extra host privilege. Combines freely with the other sources."
    },
    "artifactory_repo": {
      "type": "string",
      "description": "Artifactory repository key to pull from, e.g. \"libs-release-local\". Required when artifactory_instance is given. Scope the pull with artifactory_files and/or artifactory_folders."
    },
    "artifactory_files": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Specific files within artifactory_repo to pull, each a repo-relative path (e.g. [\"dist/app-1.0.0.tar.gz\"]). Combine with artifactory_folders; at least one of the two is required when pulling from Artifactory."
    },
    "artifactory_folders": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Folder paths within artifactory_repo to pull (e.g. [\"com/acme/app\"]). Traversed recursively unless artifactory_recursive is false. At least one of artifactory_files / artifactory_folders is required when pulling from Artifactory."
    },
    "artifactory_recursive": {
      "type": "boolean",
      "default": true,
      "description": "Whether artifactory_folders are pulled recursively (sub-folders too). Default true. Ignored when only artifactory_files are given."
    },
    "artifactory_token": {
      "type": "string",
      "description": "Artifactory API access token for artifactory_instance. Falls back to ARTIFACTORY_TOKEN from the MCP server environment. Required when pulling from Artifactory. Reaches the compiler as process environment only; never written to disk or argv."
    },
    "output_path": {
      "type": "string",
      "description": "Absolute path where the symbol library is written (the .10x.json units and the linked .10x.tar). Defaults to a fresh temp directory, returned in the result as data.payload.output.folder."
    },
    "library_name": {
      "type": "string",
      "default": "symbols",
      "description": "Base name for the linked .10x.tar library file and the compile runtimeName. Sanitized to [A-Za-z0-9_.-]."
    },
    "mode": {
      "type": "string",
      "enum": [
        "auto",
        "docker",
        "local"
      ],
      "default": "auto",
      "description": "Execution backend. `auto` (default) prefers Docker (the compiler image, guaranteed compiler flavor) and falls back to a local compiler-flavor tenx; the backend is chosen once, before the output folder is keyed. `docker` forces the image (LOG10X_COMPILER_IMAGE or LOG10X_TENX_IMAGE, default log10x/compiler-10x:1.1.136 pinned by digest, so every run goes through the same compiler). `local` forces the binary (LOG10X_TENX_PATH or `tenx` on PATH) and refuses it unless its version banner reports the compiler flavor (`compiler`, or `cloud` on an engine built before the flavor rename). Of the three shipped flavors only `compiler` can compile: `runtime` (native binary) and `runtime-jvm` (JVM-packaged runtime, the only runtime available on Windows) are both refused, since neither carries the `generate` pipeline unit. With a local install, local-folder compilation and GitHub pull (REST API + token) work out of the box; docker_images pull additionally needs a container engine (podman or docker) on the host. The docker `compiler-10x` image bundles all of those (podman included, daemonless), which is why Docker is the default."
    },
    "timeout_ms": {
      "type": "integer",
      "minimum": 10000,
      "maximum": 3600000,
      "default": 1800000,
      "description": "Hard cap on compile wall time in milliseconds. Default 1,800,000 (30 min). The engine's own whole-scan cap (scanOperationTimeout, bundled default 10 min) is raised to 90% of this value on the command line, so the engine cap follows this value (needs compiler 1.1.89+; the pinned default is 1.1.136; an older local engine keeps its 10 min cap). A re-run over unchanged local sources skips every file whose checksum already has a unit and finishes in seconds. Pulled sources (GitHub, images, Artifactory) are re-checked remotely each run and downloaded again only when the remote changed; Helm charts are rendered again every run."
    },
    "max_wait_ms": {
      "type": "integer",
      "minimum": 0,
      "maximum": 300000,
      "default": 45000,
      "description": "How long to wait inline (ms) for the compile to finish before handing back a job_id to poll. Default 45,000 (45s): small compiles, and re-runs over unchanged local sources (the engine skips files whose checksum already has a unit), finish inside this and return the library + diagnostics in ONE call. A long first compile returns a running job_id to poll with log10x_compile_status; calling this tool again with the same arguments joins the running job instead of starting a second one. Re-runs that pull remote sources are not instant: GitHub, images and Artifactory are re-checked remotely each run and Helm charts re-rendered. 0 = fire-and-forget (return the job_id immediately)."
    }
  },
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}

Source: src/tools/compile-run.ts.

Output example

Representative envelope for a compile that finished inside the wait. On completion the call returns the same typed payload that Status returns: job_id, job_status, the output (unit count + linked library), and the scan/link diagnostics. The scan-health and link-report blocks populate once the compiler-10x image carries the engine diagnostics change; on an older image they degrade to unit counts plus the log tail.

Headline (the 1-line agent-facing answer):

Compile job 9f2c1a3e-... done: 214 units → /tmp/log10x-mcp-compile/payments-9f2c1a/symbols/payments.10x.tar (4.2 MB), 3 files failed to scan.

{
  "schema_version": "1.0",
  "schema_epoch": "2026-05-25",
  "tool": "log10x_compile",
  "generated_at": "2026-05-26T15:37:46.392Z",
  "view": "summary",
  "summary": {
    "headline": "Compile job `9f2c1a3e-...` done: 214 units → /tmp/log10x-mcp-compile/payments-9f2c1a/symbols/payments.10x.tar (4.2 MB), 3 files failed to scan."
  },
  "data": {
    "status": "success",
    "decisions": { "threshold_used": null, "threshold_basis": "default" },
    "source_disclosure": {},
    "scope": {
      "window": "payments_compile",
      "window_basis": "explicit",
      "candidates_count": 214,
      "candidates_usable": 1
    },
    "payload": {
      "job_id": "9f2c1a3e-7b4d-4e1a-9f0c-2a1b3c4d5e6f",
      "job_status": "completed",
      "mode": "docker",
      "image": "log10x/compiler-10x:1.1.136@sha256:ffcd9890935804b06fcb1791994dfbb32cdb270f3e35fc423ede78ced2097b1f",
      "compiler": "docker:log10x/compiler-10x:1.1.136@sha256:ffcd9890935804b06fcb1791994dfbb32cdb270f3e35fc423ede78ced2097b1f",
      "exit_code": 0,
      "elapsed_ms": 38120,
      "timed_out": false,
      "loss_markers": [],
      "sources": "/home/dev/payments-svc + images docker.io/grafana/grafana:11.1.0",
      "output": {
        "folder": "/tmp/log10x-mcp-compile/payments-9f2c1a/symbols",
        "unit_count": 214,
        "empty_unit_count": 6,
        "library_files": [
          {
            "path": "/tmp/log10x-mcp-compile/payments-9f2c1a/symbols/payments.10x.tar",
            "bytes": 4404019
          }
        ]
      },
      "diagnostics": {
        "results_available": true,
        "phases": [
          {
            "operation": "scan",
            "status": "ok",
            "traversed_files": 1902,
            "scanned_files": 1899,
            "output_files": 214,
            "warns": 3,
            "errors": 0
          },
          {
            "operation": "link",
            "status": "ok",
            "traversed_files": 214,
            "scanned_files": 214,
            "output_files": 1,
            "warns": 0,
            "errors": 0
          }
        ],
        "scan_health": {
          "files_failed": 3,
          "failed_by_language": { "go": 2, "python": 1 },
          "failure_samples": [
            {
              "name": "internal/ledger/gen.go",
              "language": "go",
              "reason": "parse error at line 84: unexpected token"
            }
          ]
        },
        "link_report": {
          "merged_files": 214,
          "skipped_files": 0,
          "excluded_by_folder": 11,
          "excluded_by_file_name": 2,
          "merged_repos_count": 2,
          "non_merged_repos_count": 0,
          "symbols_by_type": { "class": 1840, "log": 612, "exec": 410, "enum": 96 },
          "symbols_excluded_by_type": 73
        }
      },
      "log_tail": [
        "[link] merged 214 unit files into payments.10x.tar",
        "[link] symbols by type: class 1840, log 612, exec 410, enum 96"
      ]
    },
    "human_summary": "Compile job 9f2c1a3e-... completed via docker in 38s: 214 symbol units, linked to /tmp/log10x-mcp-compile/payments-9f2c1a/symbols/payments.10x.tar (4.2 MB). 6 units are zero bytes: the engine created the unit file and wrote nothing into it, because every symbol in the file was filtered out (the default symbolTypes keep package/class/enum/log/text/exec), the file was skipped as a duplicate of one already scanned, or its scan was dropped. 3 files failed to scan (top: go 2, python 1). Example: internal/ledger/gen.go, parse error at line 84: unexpected token. Linked 214 unit files (13 excluded by folder/name filters); symbols by type: class 1840, log 612, exec 410, enum 96."
  },
  "actions": [
    {
      "tool": "log10x_validate",
      "args": { "extra_args": [["symbolPaths", "/tmp/log10x-mcp-compile/payments-9f2c1a/symbols"]] },
      "reason": "smoke-test the compiled library against a few sample event lines (supply input_lines)"
    }
  ],
  "truncated": false,
  "warnings": []
}

A long first compile of a large tree overruns max_wait_ms and returns job_status: "running" with the job_id instead. Poll it with Status. A second call with the same arguments while it runs returns the same handle with joined_running_job: true.

A run the engine stopped early (whole-scan cap, a dropped file, an aborted tree walk, an abandoned scan subprocess) exits 0 and comes back as job_status: "incomplete" with data.status: "partial" and the markers in loss_markers; see Status.

Output schema

The data.payload block inside the StructuredOutput envelope. On inline completion the shape matches Status; on overrun (or max_wait_ms: 0) the running handle carries job_status: "running" plus job_id, joined_running_job, mode, image, compiler, library_file, runtime_name, sources, started_at, timeout_ms, log_file, and output_folder.

interface CompilePayload {
  job_id: string;
  job_status: 'running' | 'completed' | 'incomplete' | 'failed' | 'timed_out';
  mode: 'docker' | 'local';
  image: string | null;
  // The identity the output folder is keyed on: `docker:<image ref>` or `local:<engine version>`.
  compiler: string | null;
  exit_code: number | null;
  elapsed_ms: number;
  timed_out: boolean;
  // Engine loss markers found in the log; non-empty on an `incomplete` run.
  loss_markers: Array<{
    kind: 'scan_operation_timeout' | 'unit_timeout' | 'traverse_aborted' | 'process_output_not_drained' | 'antlr_parse_timeout';
    count: number;
    sample: string;
  }>;
  sources: string;
  output: {
    folder: string;
    unit_count: number;
    empty_unit_count: number;
    library_files: Array<{ path: string; bytes: number }>;
  };
  diagnostics: {
    results_available: boolean;
    phases: Array<{
      operation: string | null;
      status: string | null;
      traversed_files: number | null;
      scanned_files: number | null;
      output_files: number | null;
      warns: number | null;
      errors: number | null;
    }>;
    scan_health: {
      files_failed: number;
      failed_by_language: Record<string, number>;
      failure_samples: Array<{ name: string; language: string; reason: string }>;
    } | null;
    link_report: {
      merged_files: number;
      skipped_files: number;
      excluded_by_folder: number;
      excluded_by_file_name: number;
      merged_repos_count: number;
      non_merged_repos_count: number;
      symbols_by_type: Record<string, number>;
      symbols_excluded_by_type: number;
    } | null;
  };
  log_tail: string[];
}

Envelope-level fields the agent should also read: summary.headline (1-line answer), data.status (success / partial / error), data.human_summary (prose distillation for chat), actions[] (next-call chain hints as {tool, args, reason}), truncated: boolean, schema_epoch (engine-ID stability boundary).