Skip to content

SectorOPS/Syntra

Repository files navigation

Syntra

Syntra is a self-hosted HTTP appliance for adaptive operational decisions. Each capsule is a Lycan program. The program can compute from the data it sees — EWMA forecasts on a recent series, percentile / stddev statistics, autoscale recommendations, sandboxed HTTP / SQL / file I/O — and run a strategy node over a fixed list of options. Syntra learns which option works best for each context from the delayed feedback you POST back to it.

The full positioning, including what Syntra is and is not, lives in POSITIONING.md. Read it before installing if the repositioning matters to you.

Repository relationship

This repository (SectorOPS/Syntra) is the deployable product — the appliance you install and run. The Lycan language runtime ships here as a vendored subdirectory at Lycan/; a fresh git clone of this repo is fully self-contained and builds without any other checkout.

A separate repository, SectorOPS/Lycan, is the canonical home for the Lycan language itself. Language work (new kernels, parser changes, runtime semantics, capsule format) starts there; updates flow into the vendored Lycan/ subdirectory of this repo on a periodic cadence. Anyone building or deploying Syntra clones only this repo — the Lycan repo is for people working on the language.

What that buys you, in concrete operational terms:

  • A choice that's informed by computation, not only by features the caller hand-built. A capsule can read a recent load history out of the request body, forecast it forward one step with series.ewmaForecast, derive the recommended instance count via ops.autoScaleRecommend, and run a strategy node over four scaling policies — all inside one inspectable graph. The three new demos under examples/predictive-autoscaling/, examples/anomaly-routing/, and examples/seasonal-fraud-threshold/ walk through this pattern end to end.
  • Auto algorithm selection on the choice layer. A meta-bandit runs seven candidates in parallel (Thompson, UCB1, EpsilonGreedy, Weighted, Greedy, LinUCB, LinTS) and converges on whichever performs best on your traffic. You don't pick.
  • Drift detection. Capsule-level and per-context ADWIN detectors catch regime shifts and re-warm the learner without losing the rest of the capsule's state.
  • Confidence-based refusal. When the bandit isn't confident — out-of-distribution input, prediction interval too wide — /decide returns a refusal so your service can fall back to its default policy. Configurable; disabled by default.
  • Operational hardening. Scoped auth tokens, token-bucket rate limit, Prometheus /metrics, /ready store-writability probe, JSON structured logging, backup/restore via JSON bundles. Run behind a TLS proxy.

Use it for repeated operational decisions where the best option depends on context and the outcome arrives later: how aggressively to scale a service in response to a recent load history; which routing policy to use when latency is anomalous; which fraud threshold band to apply when the chargeback resolves a week later; which LLM model handles this request; which retry or timeout policy this customer path uses; which queue / route / ranking / threshold wins for this job; which strategy works for this tenant or region.

Lycan capability surface

Capsules are authored as .lycs (Lycan source) and compiled to .lyc. The runtime exposes 26 Rust-native capability kernels; the ones that matter for the operational use cases above:

Package Kernels
math stats.mean, stats.stdDev, stats.min, stats.max, stats.percentile
math series.ewmaForecast (one-step EWMA forecast)
ops ops.autoScaleRecommend
net http.get, http.post (allow-listed hosts, private networks denied)
data sql.sqliteQuery (read-only SELECT/WITH/PRAGMA), json.get/has/len
io file.readText, file.writeText, file.exists (sandboxed)
runtime runtime.input, runtime.inputGet

Every call is policy-enforced at the runtime layer. Full registry and sandbox semantics in Lycan/src/capabilities.rs; the Lycan README groups them in a table.

🚀 Quick Start

# Pull and run
docker run -d \
  --name syntra-demo \
  -p 8080:8080 \
  -p 8787:8787 \
  ghcr.io/sectorops/syntra:demo

# Access
# Dashboard: http://localhost:8080
# API:       http://localhost:8787

That's it. Five demo capsules are pre-installed and a traffic generator drives one of them; open the dashboard to watch the lifecycle flip from Warmup to Active in the first minute and the meta-bandit panel populate trials across the seven candidate algorithms in the first five.

  • Predictive autoscaling — EWMA forecast + adaptive scaling policy
  • Anomaly-aware API routing — latency z-score + adaptive fallback
  • Seasonal fraud threshold — EWMA on fraud rate + threshold policy
  • Shared-state action embeddings — LinUCB generalization across actions
  • Hierarchical region routing — nested decisions with per-level learning

The :demo tag is built and pushed by a GitHub Actions workflow on push to main. Until that workflow has run for the first time the image is not pullable — build from source per the Local Development guide.

For production deployment, see the Helm chart or Terraform modules. For local development, see Local Development.

Integrate into your service

The retry-tuning example is the canonical Python integration. Drop-in for requests:

from syntra_retry import RetryClient

client = RetryClient(
    syntra_url="http://localhost:8787",
    capsule_path="/tenants/myteam/jobs/retry/capsules/router",
    admin_key=os.environ["SYNTRA_ADMIN_KEY"],
)

response = client.request("GET", "https://api.example.com/users")

Every request goes through /decide to pick a retry policy, then /feedback with success and latency. The client falls back to a configured default when Syntra is unreachable, refuses, or returns a malformed response — a Syntra outage degrades adaptive retry to "always fall back" without breaking the request flow.

See examples/retry-tuning/README.md for setup, customization, and tests.

What Syntra is for

Repeated operational decisions where outcomes resolve after the decision:

  • Predictive autoscaling — read a recent load history, forecast it forward, derive candidate instance counts, learn which scaling policy wins for which traffic shape. See examples/predictive-autoscaling/.
  • Anomaly-aware API routing — derive a z-score from a recent latency-series window, learn when to fall back from primary to secondary to degraded_cache_only to circuit_break. See examples/anomaly-routing/.
  • Seasonal fraud thresholds — EWMA-forecast next-window fraud rate, pick a threshold-adjustment policy, learn from chargebacks that resolve days later. See examples/seasonal-fraud-threshold/.
  • LLM model routing — pick cheap_fast vs balanced vs expensive_accurate per request, learn quality / latency / cost tradeoffs per context.
  • HTTP retry policy — pick a retry strategy per endpoint based on recent failure rate and p99 latency. (See the demo and the integration example.)
  • Queue / route / ranking selection — pick which downstream handler or ranking weight wins for this customer or context.

What Syntra is not

It is not for:

  • Arbitrary forecasting — Syntra ships exactly one forecasting kernel (series.ewmaForecast, one-step EWMA with one alpha parameter). It is not a substitute for a proper time-series model.
  • A managed service — self-hosted Docker container, single process, local-filesystem store. Run behind a TLS proxy.
  • Modern-data-stack scale — designed for hot-path decisions in the hundreds-to-low-thousands per second on commodity hardware. No clustering.
  • A metric collection / observability system — the optional sidecar/ reads from Prometheus / Datadog / SQL but does not store time series or replace those tools.
  • A model platform — no GPU, no training loop, no model registry, no fine-tuning.
  • Supervised problems with ground-truth labels at prediction time — use a model framework.
  • Continuous-valued action spaces — Syntra picks among discrete options. (A bucketed ActionSpace::Continuous exists for the continuous-bucket case; see Phase G+H entry in CHANGELOG.)
  • One-shot decisions — without a feedback loop, there's nothing to learn.
  • A replacement for experiment / feature-flag platforms — those tell you whether to ship X; Syntra picks which option to use once X is shipped. Adjacent to Statsig / Eppo / GrowthBook / LaunchDarkly, not a replacement.

How the learning layer works

Each capsule moves through a lifecycle: Warmup → Active → Frozen.

  1. Warmup — Syntra runs uniform random selection for the first ~30 feedback rounds, watches reward shape, and characterizes the problem (binary / continuous / sparse). It picks an initial algorithm automatically.
  2. Active — a rate-adaptive meta-bandit runs seven candidate algorithms in parallel: Thompson, UCB1, EpsilonGreedy, Weighted, Greedy, and (for feature-context capsules) LinUCB and LinTS. The meta-bandit converges on whichever candidate performs best on this capsule's data.
  3. Frozen — operator-triggered; the bandit stops learning but continues serving decisions from the current weights.

Drift detection runs at two scopes: a capsule-level ADWIN detector triggers re-warmup when reward distribution shifts globally, and per-context ADWIN detectors reset just the affected context bucket on narrower shifts.

Refusal (Phase E, opt-in) wraps reward predictions in split-conformal intervals and tracks out-of-distribution scores per context. When the interval is too wide or the input is OOD, /decide returns {"refused": true, "confidence": {…}} and your service falls back. See refusal config below.

Configuration

A learning.json per capsule controls the learner. Most fields default to sensible values; the ones you usually touch:

{
  "contextSpec": {
    "type": "features",
    "features": [
      {"name": "recent_failure_rate", "type": {"kind": "continuous", "range": [0, 1]}},
      {"name": "p99_latency_ms",      "type": {"kind": "continuous", "range": [0, 5000]}},
      {"name": "hour",                "type": {"kind": "cyclic", "period": 24.0}}
    ]
  },
  "refusal": {
    "enabled": true,
    "coverage": 0.95,
    "maxIntervalWidth": 0.5,
    "oodThreshold": 0.8
  }
}
  • contextSpecdiscrete (string contextKey, the default) or features (typed vector; enables the LinUCB candidate in the meta-bandit).
  • refusal — enabled-off by default. When on, the response carries a confidence block with oodScore, intervalWidth, and refused: bool.

PUT it at any time: PUT /tenants/{t}/jobs/{j}/capsules/{c}/learning.

API

# Install a capsule
curl -X POST http://localhost:8787/tenants/acme/jobs/routing/capsules/router/install \
  -H "Authorization: Bearer $LYCAN_ADMIN_KEY" \
  --data-binary @router-capsule/program.lyc

# Optional: install the reward spec so feedback can use the components form
curl -X PUT http://localhost:8787/tenants/acme/jobs/routing/capsules/router/reward_spec \
  -H "Authorization: Bearer $LYCAN_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @router-capsule/reward_spec.json

# Get a decision (discrete-context capsule)
curl -X POST http://localhost:8787/tenants/acme/jobs/routing/capsules/router/decide \
  -H "Authorization: Bearer $LYCAN_ADMIN_KEY" \
  -d '{"contextKey":"support-low-cost"}'
# → response carries decisionId, decisions[], oodScore, refused, confidence

# Get a decision (feature-context capsule)
curl -X POST http://localhost:8787/tenants/acme/jobs/routing/capsules/router/decide \
  -H "Authorization: Bearer $LYCAN_ADMIN_KEY" \
  -d '{"features":{"recent_failure_rate":0.15,"p99_latency_ms":1200,"hour":3.0}}'

# Send feedback
curl -X POST http://localhost:8787/tenants/acme/jobs/routing/capsules/router/feedback \
  -H "Authorization: Bearer $LYCAN_ADMIN_KEY" \
  -d '{"decisionId":"dec_abc123","reward":0.85}'

# Inspect learned state
curl http://localhost:8787/tenants/acme/jobs/routing/capsules/router/report   -H "Authorization: Bearer $LYCAN_ADMIN_KEY"
curl http://localhost:8787/tenants/acme/jobs/routing/capsules/router/memory   -H "Authorization: Bearer $LYCAN_ADMIN_KEY"
curl http://localhost:8787/tenants/acme/jobs/routing/capsules/router/contexts -H "Authorization: Bearer $LYCAN_ADMIN_KEY"

See docs/api.md for the full surface including evolution, chaos, evaluate, and audit endpoints.

Authoring capsules

Capsules are authored as YAML and compiled to a deployable .lyc by the syntra author command:

name: llm-router
options:
  - cheap_fast
  - balanced
  - expensive_accurate
reward:
  type: continuous
  range: [-1.0, 1.0]
syntra author my-capsule.yaml --out-dir ./my-capsule/
# emits my-capsule/program.lyc + sidecar JSON

Then POST program.lyc to /install and PUT a learning.json to attach a feature-context spec or enable refusal.

Smoke-test a spec locally before deploying:

syntra simulate my-capsule.yaml --rounds 5000 --true-arm-rewards "0.2,0.5,0.7" --seed 7

Data model

tenant / job / capsule

tenant   = organization or environment
job      = independent learning context (same capsule, different memory)
capsule  = the compiled program + its learned state

Persistent store

syntra-store/
  tenants/{tenant}/jobs/{job}/capsules/{capsule}/
    current.lyc       — the graph binary
    policy.json       — runtime permissions
    memory.json       — learned weights, meta-bandit, calibrators, OOD detectors
    learning.json     — algorithm config (contextSpec, refusal, …)
    warmup.json       — lifecycle state
    audit.jsonl       — mutation log
    decision.jsonl    — decision log (carries refused flag and confidence)
    feedback.jsonl    — feedback log
    snapshots/        — pre-mutation backups

Container is disposable. The store survives restarts. The memory.json schema is at version 7, with backward-compat readers for v2 through v6.

Shadow mode

Syntra can run beside an existing application without taking control:

  1. Your app sends request context to /decide.
  2. Syntra returns a suggested option and a decisionId.
  3. Your app continues with its current production decision.
  4. When the real outcome resolves, your app posts /feedback with the decisionId and the observed reward.
  5. Syntra updates memory and exposes the learned state in /report, /contexts, the admin console.

That makes it possible to prove the adaptive layer before letting it influence live behaviour.

Admin console

Browser UI at /admin:

  • Tenant / job / capsule navigation
  • Live strategy weight visualization
  • Decision and audit log inspection
  • Policy enforcement status
  • Context memory viewer
  • Capsule deletion and log purging

Security

  • All routes except /health require Authorization: Bearer token.
  • Capsule policy enforced at runtime (file sandbox, network sandbox, SSRF protection).
  • File capabilities scoped to capsule working directory.
  • HTTP capabilities require explicit allowed_hosts. Private networks denied by default.
  • Constant-time key comparison. Failed auth logged.
  • Server refuses startup without an admin key unless --dev-mode (binds localhost only).

Not yet production-hardened for direct public-internet exposure. Run behind a TLS proxy. The path to production hardening is tracked in #2 and starts with the threat model in SECURITY.md.

Operating

When weights look wrong, inspect the data trail before changing the capsule:

  1. /report for current strategy weights.
  2. /contexts to confirm the request landed in the expected contextKey.
  3. decision.jsonl for what Syntra suggested.
  4. feedback.jsonl for which option was rewarded and whether the reward sign is correct.
  5. audit.jsonl for installs, policy changes, deletes, refusals, and change-detection events.

See docs/operating.md for the full operator checklist and docs/deployment.md for production deployment.

Field use

Syntra is currently running in shadow mode against MoEfolio.ai, a public AI trading panel that produces a verdict per cycle and resolves outcomes against the market after a delayed window.

Across recent verdicts, Syntra has learned non-trivial weights against the panel's gate and is expressing structured disagreements, primarily that the gate may be over-cautious on some BUY signals. Whether those disagreements are correct requires more resolved outcomes than are currently available; the experiment is ongoing.

Architecture

Syntra is built on Lycan, a graph-execution runtime that ships in this repo as the Lycan/ subdirectory. Capsules are authored as YAML and compiled to Lycan's binary format automatically — most Syntra users never interact with Lycan directly. If you want to dig into the substrate, the source lives at Lycan/src/ and the language reference at Lycan/README.md.

Examples

Operational-kernel demos — series.ewmaForecast, stats.percentile, stats.mean / stdDev, ops.autoScaleRecommend feeding into an adaptive choice. Each ships a capsule.yaml, a program.lycs, a learning.json, and a README walking through install / decide / feedback.

Integration packs — Python and language-client examples consuming Syntra over HTTP:

Bash demos and tooling:

Substrate-level demos (read these if you want to see the Lycan kernels exercised directly, not through a Syntra capsule):

Sidecar

sidecar/syntra-ingest, an optional metrics-ingestion sidecar. YAML-configured, polls Prometheus / Datadog / SQL / file sources on a per-source interval, exposes GET /features/current returning the latest snapshot. Best-effort, stateless, single-process. Use it if your capsule needs feature values that live in those systems and you don't want to embed four client libraries inside your hot path. Not a metric store.

Roadmap & license