The RSI platform cycle — five layers, one hand-off¶
The loop kernel, compute allocator, promotion decision, capability foundry and
continual experience are separately tested. RSIPlatform is the narrow system
seam that makes them one cycle:
from agentdescent import PlatformConfig, RSIPlatform
from agentdescent.durable_runs import SqliteRunStore
platform = RSIPlatform(
SqliteRunStore("runs.sqlite3"),
PlatformConfig("tenant-a", "ws-1", max_generations=3, target=0.9),
compute=controller, # ComputeController
foundry=foundry, # CapabilityFoundry (optional)
experience=engine, # ExperienceEngine (optional)
decision_log=log, telemetry=telemetry)
run_id = platform.submit()
report = platform.run_cycle(
run_id, "worker-1",
step=my_step, # (LoopContext, ComputeDecision) -> LoopObservation
evidence=my_evidence, # (LoopReport, best) -> PromotionRequest
capability=my_capability, # (LoopReport, PromotionDecision) -> CapabilityRequest
)
report.complete, report.to_dict()
What the platform owns¶
- Compute before the step. It calls
ComputeController.choose()with the cycle'sComputeContext, passes the decision tostep, observes the reward/cost, and writescompute_action/compute_option/compute_model/featuresinto the observation evidence. A caller's ownactionkey is preserved. Compute observation is at-least-once: a retried generation callsobserveagain, so callers that retry a failed step should account for that. - The durable loop.
LoopKernelruns the generations;applyandrollbackare passed straight through. - Promotion. The best accepted generation is handed to the
evidencecallback;promote()makes the decision with the platform's rules. - Foundry. The
capabilitycallback receives the loop report and promotion decision; a returnedCapabilityRequestis admitted by the foundry. - Experience. Every generation becomes an
ExperienceRecord(reward map fromevidence["rewards"], action fromevidence["compute_action"]), is ingested once, and the engine updates with the supplied validation/anchor rows. If no validation rows are supplied, the cycle's own records are used and the report marksself_validated=True— that is a warm-up, not held-out evidence. Anchors default to the most recent pre-cycle record; on the first cycle they bootstrap from the cycle itself. A campaign reuses the same rows every cycle unless the caller supplies fresh ones. - Completeness. A configured layer that does not produce a decision makes
completefalse. A layer skipped by an upstream decision (for example the foundry after a rejected promotion) is named inskippedanddetail, never silently skipped. - Gate / risk gate. The constructor's optional
gate/risk_gateare passed straight toLoopKernel; they act on the loop's control decision, not on promotion.
What it deliberately does not do¶
It adds no decision logic. It does not invent evidence, rerun a gate, or decide
whether a promotion is good — it only hands the tested outputs to the next
layer. A cycle with no evidence, capability or experience runs just the
loop and is still complete.
From the CLI and the MCP server¶
The same campaign is available as a product surface:
agentdescent platform init --out platform.json --demo
agentdescent platform plan --spec platform.json
agentdescent platform run --spec platform.json --json --out report.json
A platform spec names the SQLite run database, the two configs, and the
callbacks by module:attribute:
{
"runs_db": "platform-runs.sqlite3",
"platform": {"tenant": "t1", "workspace": "ws", "max_generations": 3},
"campaign": {"max_cycles": 5, "target": 0.9, "patience": 2},
"compute": "mypkg.wiring:compute",
"step": "mypkg.steps:step",
"evidence": "mypkg.steps:evidence",
"capability": "mypkg.steps:capability",
"foundry": "mypkg.wiring:foundry",
"experience": "mypkg.wiring:experience"
}
compute may be omitted (a single direct strategy over a zero-cost local
model) or written inline:
{"models": [{"name": "local", "cost": 0.0}],
"options": [{"name": "direct", "action": "direct", "calls": 1}]}
Budgeted PRM/verifier search as the step¶
step may be a {"budgeted": ...} block instead of a callable ref. It builds
the existing BudgetedStep from budgeted_loop:
"step": {
"budgeted": {
"fabric": "mypkg.search:build_fabric",
"state": "mypkg.search:state",
"config": {"budgets": [8, 16, 32], "min_gain_per_expansion": 0.0001,
"patience": 2},
"risk": "mypkg.search:risk"
}
}
fabricis a zero-argument builder (or a readySearchFabric).stateis(LoopContext) -> state;configisBudgetedStepConfig;riskandevidence_ofare optional callables.
The platform passes its own (context, compute_decision) to the step; the
budgeted adapter ignores the compute decision because the search owns its
budget. plan validates the refs and config without calling the builders, so a
builder that raises is reported at run, not plan. scale_budget defaults to
true, so the actual budgets are multiplied by the loop controller's
budget_scale (the configured numbers are the base budgets).
plan resolves every ref and validates the configs without running. run
returns the full PlatformCampaignReport with schema_version: 1. The MCP
platform tool takes the same object and dry_run=true for the plan step.
The spec executes the Python callbacks it names. For MCP, the operator must set
AGENTDESCENT_SURFACE_ALLOW to the allowed module prefixes (an empty value
refuses every ref), and AGENTDESCENT_SURFACE_WRITE_DIRS to the directories the
server may create reports/DBs in. plan resolves refs without calling builders
or creating the SQLite database.
Inspect a campaign after it finished¶
platform run keeps every cycle in its SQLite run database, so a process that
has exited can still be inspected without SQL:
agentdescent platform status --runs-db platform-runs.sqlite3 \
[--tenant t1] [--workspace ws] [--limit 20] [--json]
agentdescent platform status --runs-db platform-runs.sqlite3 --run-id RUN_ID
Without --run-id, the command lists the most recent runs, newest first, with
status, best reward, generation count, accepted count and stop reason. With
--run-id it returns that run's full LoopReport. It only reads the database
and does not need the compute, foundry or experience callbacks. A missing
database or unknown run is an input error (exit 2). The MCP platform_status
tool exposes the same read-only view.