oxy-compile-boundary

star 202

Use when adding a new YAML entity type to Oxy (e.g. a new file extension under `crates/oxy-compile/src/walker.rs` like `.foo.yml`), when introducing a new runtime read site that walks the workspace filesystem, when wiring a new handler that calls `ConfigManager::resolve_*` or `fs::read_to_string(workspace_path...)`, or any time someone proposes a feature that "just reads from the workspace dir." Also triggers on phrases like "new file extension", "add a YAML config", "load this from disk", "scan the workspace for", "read the YAML file" — the compile boundary expects every NEW workspace artifact to be a row in Postgres, not a per-request FS read.

oxy-hq By oxy-hq schedule Updated 6/12/2026

name: oxy-compile-boundary description: Use when adding a new YAML entity type to Oxy (e.g. a new file extension under crates/oxy-compile/src/walker.rs like .foo.yml), when introducing a new runtime read site that walks the workspace filesystem, when wiring a new handler that calls ConfigManager::resolve_* or fs::read_to_string(workspace_path...), or any time someone proposes a feature that "just reads from the workspace dir." Also triggers on phrases like "new file extension", "add a YAML config", "load this from disk", "scan the workspace for", "read the YAML file" — the compile boundary expects every NEW workspace artifact to be a row in Postgres, not a per-request FS read.

Compile every workspace artifact to Postgres

Oxy's runtime no longer walks the workspace filesystem on customer-facing requests. PR #2460 (the compile boundary work) made every YAML entity addressable as a *_definitions row keyed by revision_id. The IDE Compile button promotes a revision and every read site serves from Postgres until the next compile. The boundary is always on — there are no feature flags; readers fall through to the filesystem on any miss.

This skill is the rule for anyone adding a new file type the runtime needs to read. Skip this skill only when the read is genuinely IDE-only (file CRUD, git ops) or a one-time onboarding write.

The contract: when you add a new .foo.yml

You owe all five of these before the feature ships. Skipping any one of them means the new file walks FS on every customer request — exactly what we just stopped doing.

  1. Walker — add a FileKind::Foo variant in crates/oxy-compile/src/walker.rs and a glob in discover() so a compile pass finds the file. Root-only files go into the explicit if path.is_file() block (see Config and MonitorConfig as templates). Glob patterns go through push_glob.

  2. Compile output — add CompiledRow::Foo(CompiledFoo { ... }) in crates/oxy-compile/src/compile.rs, a match arm in compile_one(), and a row_dedupe_key entry (or None if the file is a singleton like config.yml / .monitor.yml). For named entities (agents, views, topics) use compile_named_yaml — it handles the name/file_path/definition shape uniformly. For singletons, write a small compile_foo() that parses and emits one row.

  3. Schema — append the new table to the squashed migration crates/migration/src/m20260606_000002_create_compile_boundary.rs (BOTH up() and the reverse-order down()). Add a MonitorConfigs-style DeriveIden enum at the end of the file. The FK convention is from(Foo::Table, Foo::RevisionId).to(Revisions::Table, Revisions::RevisionId).on_delete(ForeignKeyAction::Cascade). Add the matching entity file under crates/entity/src/foo_definitions.rs and register it in crates/entity/src/lib.rs.

  4. Writer — add a CompiledRow::Foo(f) => foos.push(...) arm in crates/oxy-compile/src/writer.rs, declare the let mut foos = Vec::new() near the top of the function, and add the bulk-insert call near the end. The order mirrors the existing kinds.

  5. Reader + handler wiring — add resolve_foo (and list_foos if applicable) to crates/app/src/server/api/compiled_reader.rs. They follow the open_compiled_revisionfind_by_id shape. Then wire the runtime handler:

    • If the handler accepts a string / struct: read the compiled row, deserialise from the JSONB definition, fall through to FS on miss.
    • If the handler accepts a Path (e.g. an external library like airlayer or oxy_metric_monitoring): use the materialiser pattern in crates/app/src/server/api/semantic_scan.rs — write the Postgres bytes to a tempfile::TempDir, pass that path, hold the TempDir guard until the call returns.

open_compiled_revision already handles the branch carve-out + revision lookup for every reader — you do not need to re-implement it per-surface.

S3 blob storage (semantic views / topics only)

Large semantic view / topic bodies move to S3 when OXY_COMPILE_BLOB_S3_BUCKET is set. The compile worker uploads each body to s3://<bucket>/workspaces/<workspace_id>/{semantic_views,semantic_topics}/<name>-<sha[..32]>.yml and stores the key in semantic_views.compiled_sql_blob_key / semantic_topics.compiled_sql_blob_key. The materialiser (crates/app/src/server/api/semantic_scan.rs) prefers the S3 blob over the in-row JSONB. When the env var is unset, blob_key is NULL and the in-row definition is the canonical body — Postgres-only deployments work unchanged.

If you add a new entity type whose definition routinely tops tens of KB (the way semantic views do), follow the same shape: add a nullable compiled_sql_blob_key column, wire the upload in oxy-compile's writer, and extend oxy_compile::blob_store::BlobKind. Small definitions (apps, agents, procedures) stay JSONB-only — the S3 round-trip costs more than the row bloat at typical sizes.

Why this matters

The original FS read pattern was the dominant runtime cost at any meaningful workspace count. The IDE works on a singleton instance (FS-backed working copy); the customer-facing oxy-serve fleet must never read workspace files because those instances may not have them. Every new file type you skip the compile boundary on is a new failure mode under multi-instance.

What's NOT in scope

Skip this contract only when:

  • The file is purely IDE-editor state (already on the singleton, fine).
  • The artifact is a generated build product (charts, parquet caches, customer-app bundles) — those belong in S3, not the compile boundary.
  • The read happens exactly once at server startup, not per-request (startup walks of OXY_STATE_DIR are fine).

If you find yourself adding a glob::glob(workspace_path) or fs::read_to_string(workspace_path.join(...)) in a request handler and the path is not already a compiled entity, you are on the wrong path. Open this skill and add the file type to the compile boundary first.

Related skills + design docs

  • oxy-scaling-design — broader multi-instance context.
  • oxy-task-spec-default — long-running work goes on the worker fleet, not in handlers.
  • internal-docs/compile-boundary.md — operator runbook (flags, kill switch, read routing, code map).
  • The compile worker entry point: crates/agentic/pipeline/src/compile_worker.rs.
  • The hybrid reader: crates/app/src/server/api/compiled_reader.rs.
  • The materialiser pattern: crates/app/src/server/api/semantic_scan.rs.
Install via CLI
npx skills add https://github.com/oxy-hq/oxygen --skill oxy-compile-boundary
Repository Details
star Stars 202
call_split Forks 24
navigation Branch main
article Path SKILL.md
More from Creator