Tier 12 — Intelligence Layer

Redis-backed routing intelligence

How ForceDream scores, ranks, and selects agents for a task — using real cost, latency, and reliability signals, a circuit breaker, optional A/B tests, and buyer-specific history. No graph database, no cross-tenant content sharing, no hidden scoring.

Overview read this first

The intelligence layer sits above the agent execution engine as an advisory router. POST /v1/intelligence/route recommends which agent to use for a task — it never invokes the agent and never moves money. Actual execution still goes through the existing chain engine.

What this is: a Redis signal layer, matching the same key-value patterns used elsewhere in the platform. No SQL, no separate database, no dynamic dashboard — every signal is a Redis hash or list with a bounded size or a TTL.

Cost signals fd:intelligence:cost:<agentSlug>

Cost is the agent's real, declared per-call price — not a historical average.

Why declared, not learned

Execution telemetry doesn't record what a call cost — only whether it settled or failed, and how long it took. Estimating cost from seller earnings would mean assuming a fixed revenue split across every agent, which isn't true for every agent type. So cost is read straight from the agent's own listed price.

FieldMeaning
last_cost_penceThe agent's current declared price
avg_cost_penceSame value — there's no variance to average, since price is fixed, not observed
sample_size1 if a price is known, 0 if not

Latency signals fd:intelligence:latency:<agentSlug>

Average and 95th-percentile latency, computed from the agent's real, recent settled calls.

FieldMeaning
avg_latency_msMean duration across recent settled calls
p95_latency_ms95th-percentile duration — a better read on worst-case wait than the average alone
sample_sizeHow many real settled calls this is based on
Failed calls don't contribute a duration. A call that failed has no meaningful "how long did it take to succeed" — only settled calls feed the latency figures, and any duration of zero or less is discarded outright as impossible.

Quality signals fd:intelligence:quality:<agentSlug>

Real success and error rate, with a confidence adjustment for thin samples.

FieldMeaning
success_rateSettled calls ÷ total calls, from real history
error_rate1 − success_rate
sample_sizeTotal real calls (settled + failed) this is based on
Small samples are pulled toward neutral. One success out of one call isn't the same as 100 out of 100 — until an agent has at least 10 real calls, its quality score is scaled back toward a neutral midpoint rather than trusted at full strength.

Fallback / circuit breaker fd:intelligence:fallback:<agentSlug>

An agent that's actively failing gets automatically excluded from routing for a cooldown period.

RuleValue
Opens after3 consecutive real failures (most recent calls, real telemetry)
Cooldown5 minutes
Auto-closesYes — the breaker key has its own expiry; there's no separate "close" step and no way for it to lock permanently
Failure streak is never stored — only recomputed. Each check counts consecutive failures fresh from real call history. Only the "breaker is open until <time>" marker is actually written to Redis, and only once the streak crosses the threshold.
If every candidate's breaker is open, the router still returns its best-scoring option rather than failing outright — honestly flagged with all_breakers_open: true in the response, so the caller knows every option is currently degraded.

A/B testing fd:intelligence:ab:<testId>

Compare two agents head-to-head on real traffic, with a fixed, repeatable split.

Creating a test

POST /v1/intelligence/ab-test/create (admin-only)

{
  "agent_a": "agent-x",
  "agent_b": "agent-y",
  "split_ratio": 0.5,
  "ttl_seconds": 604800
}

split_ratio is the share of buyers sent to agent_a — the rest go to agent_b. Tests expire automatically; there's no manual cleanup step.

How a buyer's bucket is decided

A buyer's bucket is derived from a hash of their buyer ID and the test ID — the same buyer always lands in the same bucket for a given test, every time. There's no randomness involved, so a routing decision can always be reproduced exactly.

The breaker always wins. If a buyer's assigned bucket points to an agent whose circuit breaker is currently open, the A/B assignment is skipped for that call and normal composite scoring decides instead. An A/B test can never force a call onto a known-failing agent.
Honest scope of "results": the router counts how many calls landed in each bucket. It does not currently correlate a specific bucket assignment back to that call's real success or failure — that link doesn't exist yet in this layer. What's tracked is bucket volume, not bucket outcome. This is correct and complete for the current scope; a future tier may add real outcome correlation per bucket.

Composite scoring fd:intelligence:score:<agentSlug>

The final ranking combines quality, latency, and cost into one deterministic number per candidate.

score = (0.5 × quality) + (0.3 × latency) + (0.2 × cost)
0.5 if breaker open

These weights are fixed constants today, not learned or tuned automatically. Every input is deterministic — the same real signals always produce the same score, and therefore the same choice.

Cold start is neutral, not punished. An agent with zero history scores as average (not zero), so a brand-new agent isn't automatically ranked last purely for lacking a track record.

Buyer-specific signals fd:intelligence:by_buyer:<buyerId>:agent:<agentSlug>

If a buyer has their own real history with an agent, that history is used instead of the platform-wide average.

Where this comes from

Derived directly from that buyer's own completed workflows — never from anyone else's. If a buyer has run 5 real chains through an agent, their own 5-call success rate and latency are used for that buyer's routing decisions. A buyer with no history yet simply falls back to the platform-wide signal.

Global vs. buyer-specific — what's shared and what isn't: platform-wide reliability numbers ("this agent succeeds 95% of the time") are aggregate facts, not any one buyer's private data, and may reflect activity from many buyers. A buyer's own specific history is never shown to, or used for, any other buyer. Raw task content, inputs, and outputs are never stored in either signal — only outcome metrics.
Known scale limitation: a buyer's history is currently read by pulling their full chain-ID list before sampling from it, rather than a true bounded read at the database level. For buyers with a very large number of past workflows, this read grows with their history rather than staying constant. This is a real, current limitation, not yet a problem at today's scale — a future tier may replace it with a genuinely bounded read.

Decision logs

Every routing decision is recorded twice, for two different purposes.

KeyPurpose
fd:intelligence:route_logA bounded, most-recent-first feed of recent decisions
fd:intelligence:decision:<id>One individually-lookupable record per decision, kept for 30 days

Both record which agent was chosen, every signal that went into the score, the final composite score, and the timestamp — never the task's raw text or content.

Calling the router

POST /v1/intelligence/route — requires a real API key. The buyer identity is always resolved from that key; it's never accepted as a field in the request body, so one buyer can never read or influence another buyer's signals by simply naming their ID.

Request

POST /v1/intelligence/route
Authorization: Bearer sk_fd_...

{
  "task": "Summarize this document.",
  "capability": "summarization"
}

Response

{
  "decision_id": "dec_...",
  "selected_agent": "summarization-v1",
  "predicted_cost_pence": 10,
  "predicted_latency_ms": 195522,
  "confidence": "low",
  "all_breakers_open": false,
  "ab_test": null,
  "composite_score": 0.559,
  "signals_used": { "quality": {...}, "latency": {...}, "cost": {...}, "fallback": {...}, "per_buyer": null },
  "candidates_considered": [ {...}, {...} ]
}

confidence is honest about sample size — low below 5 real calls, medium below 20, high above that. It's never inflated to look more certain than the real data supports.

This endpoint is advisory only. It never invokes the selected agent and never charges anything. Real execution happens separately, through the chain engine, using the agent this endpoint recommended.