feat(simulate): version agents, environments and scenario suites and pin runs to them by JayaSurya-27 · Pull Request #3533 · future-agi/future-agi · GitHub
Skip to content

feat(simulate): version agents, environments and scenario suites and pin runs to them - #3533

Open
JayaSurya-27 wants to merge 39 commits into
devfrom
feat/environment-versioning
Open

JayaSurya-27 wants to merge 39 commits into
devfrom
feat/environment-versioning

Conversation

@JayaSurya-27

@JayaSurya-27 JayaSurya-27 commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds versioning to simulation environments. An environment now has three versioned pieces: the agent (source, prompt and credential set), the environment (contract, world and sub-goals) and the scenario suite. Every run is pinned to one version of each. People can see every version and what changed in it, pick versions when starting a run (with warnings that never block), and roll back the environment or suite default. Agent changes (a GitHub push, an uploaded folder, a prompt or credential edit) become new agent versions that are checked and then offered to each environment through a nudge in the builder chat; answering yes rebuilds the environment as a new version. Builder edits are kept as unsaved drafts until Save. All three flags are off by default, so nothing changes on dev until the backfill has run and HARNESS_VERSIONING_ENABLED is turned on.

The companion agent-learning-kit PR (feat/environment-versioning -> dev) must deploy with this one: future-agi/agent-learning-kit#180

Linked issues

Linear:
Part of TH-7948

AI use

AI use:

Type of change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📖 Documentation only
  • 🧹 Chore / refactor (no user-visible change)
  • 🚀 Performance improvement
  • 🧪 Test-only change

E2E coverage

E2E: exempt (harness-gap: the environment versioning flows are kept out of this PR)


1) What changes were done

With HARNESS_VERSIONING_ENABLED off, users see no change. With it on, the workspace gains a Versions tab, a run dialog, a roll-back menu, nudge cards, and a Save / Discard bar. The API adds the endpoints in the table below, adds pinned versions to run responses, and changes secret-values/ (see Backward compatibility). Five migrations; no data migration.

A. Tables and migrations — simulate/models/harness_versioning.py, simulate/models/hosted_harness.py

Table Holds
simulate_harness_environment_version One environment version: status (draft / active / discarded), origin, content hash, archive key, parent, summary, diff
simulate_harness_suite_version One scenario suite version, same base fields plus certify_hash, scenario_count, authored_against
simulate_harness_suite_member The scenarios of a suite version, by stable scenario_key
simulate_harness_suite_validation Proof of a suite version against an environment version
simulate_harness_agent_version_runtime Side row for an AgentVersion: source, prompt hash, encrypted credential blob, credential names and fingerprints, preflight status and report
simulate_harness_nudge A nudge on an environment: target, state, facts (names only), base and result versions, rebuild turn
simulate_harness_assembled_bundle Cache of sealed bundles per (agent, environment, suite) version combination
  • HostedHarnessJob gains agent_version, environment_version, suite_version and assembled_bundle FKs (all SET_NULL, nullable). On an environment job they are the defaults; on a run job they are the run's pins.
  • HostedHarnessConversationLease gains agent_version (the version whose source the sandbox holds).
  • AgentDefinition.create_version accepts an optional version_number.
  • Migrations:
    • 0098_harness_versioning_tables — the seven tables
    • 0099_harness_versioning_pins — the job and lease FKs
    • 0100_harness_agent_runtime_hash_scope — rescopes the agent runtime hash unique constraint
    • 0101_harness_agent_runtime_check_started_at — check_started_at on the agent runtime row
    • 0102_harness_nudge_turn — command_message and closed_at on the nudge

B. Versioning core — simulate/services/harness_versioning/

  • hashing.py: content identity for environment, suite and agent; assembly_key for a version combination. simulate/services/canonical_json.py holds the shared canonical JSON digest.
  • archives.py: splits an authoring tree into environment and suite archives, composes them back, and stores them under content-addressed keys in the object store. No git is used for versions.
  • versions.py: record_authoring records environment and suite versions from an authoring archive (identical content reuses the existing version); move_default and set_defaults move defaults with a conditional update, so concurrent moves get a conflict instead of overwriting.
  • diffs.py: what changed between two environment, suite or agent versions (contract fields, tools, scenarios added / removed / changed).
  • read.py: the version lists, a diff, suite members and run_version_labels for run responses; require_versioning returns 404 while the flag is off.

C. Backfill — simulate/management/commands/harness_versions_backfill.py, harness_versioning/backfill.py, backfill_check.py

  • Gives existing environments their first agent, environment and suite versions and pins their past runs where the revision matches. Idempotent: an environment already versioned only gains what it lacks.
  • --check is a read-only dry pass (rolled-back, read-only transactions; output carries ids, counts and codes, never scenario text, prompts or values). Also --job <id> (repeatable), --json, --output <file>.
  • model_hub/apps.py adds the command to OPERATOR_STARTUP_MUTATION_COMMANDS and lets the exact --check form through as read-only.
  • The write runs as a one-off operator process (INSTALLATION.md): docker compose exec -e SERVICE_TYPE=bootstrap -e STARTUP_DB_MUTATION_MODE=operator -e NO_STARTUP_DB_MUTATIONS=false backend python manage.py harness_versions_backfill (app in Standalone), or on Helm kubectl -n futureagi exec deploy/futureagi-backend -c backend -- env SERVICE_TYPE=bootstrap STARTUP_DB_MUTATION_MODE=operator NO_STARTUP_DB_MUTATIONS=false python manage.py harness_versions_backfill. The running services keep NO_STARTUP_DB_MUTATIONS=true.
  • It is a manual command, not a migration, so a backfill problem cannot block other migrations.

D. Agent versions and credentials — simulate/services/harness_agent_versions.py, harness_credentials.py, views/harness_job.py

  • Each environment gets its own agent version, pinned for its runs and builds. Environments on the same provider assistant share one agent definition.
  • The create request carries the agent's variable values (credential_values); they are stored encrypted only on the agent version's side row. Everything else (diffs, reports, logs, task inputs) carries names and keyed fingerprints.
  • A new version is checked by preflight_harness_agent_version (simulate/tasks/harness_agent_versions.py) before it can become a default. Failed or stuck checks can be retried or discarded.
  • secret-values/ now requires environment_id, creates the environment's next agent version from the new values and returns 202 with that version.
  • harness_credentials.py resolves names-only refs (harness-agent-version:<name>) from the pinned version's credential set; existing platform-vault refs keep resolving.

E. Agent change detection — simulate/services/harness_agent_changes.py, harness_github.py, views/harness_github_webhook.py, simulate/signals.py

  • A push to a watched public GitHub repository (signed webhook at api/harness/github/webhook/ or the 10-minute poll_harness_github_heads schedule), an uploaded folder, a prompt edit or a settings edit creates the next agent version through create_agent_version.
  • GitHub reads are unauthenticated and share one hourly budget, with per-owner and per-org limits. Sources are tracked by repository id. The first sight of an existing environment records its head instead of creating a version.
  • Discards and roll backs hold per environment, so a discarded commit is not offered again.
  • A post_save receiver on AgentVersion hands new or activated versions to on_agent_version_saved after commit.
  • GITHUB_APP_WEBHOOK_SECRET is new; unset, the webhook is ignored.

F. Nudges and rebuild — harness_versioning/nudges.py, rebuild.py, simulate/tasks/harness_nudges.py

  • A new default agent version, or a newer version an environment can pick, raises an environment nudge shown as a card in the builder chat. Facts list changed files, prompt, settings and variable names, never values; GitHub patches are redacted against the agent's credential values.
  • Moving the environment default raises a scenarios nudge. One environment nudge is pending at a time, offering only the newest version. Nudges are re-checked when a build or extend completes and closed when the environment is deleted.
  • Yes queues a builder turn with a names-only briefing. Its result lands as new environment or scenario versions, reports nothing to change, or fails. Unsaved edits are held aside during the turn and restored on every outcome that does not land.
  • Stop works from the card, the agents panel and the composer. The 30-second tick_harness_nudges schedule closes rebuilds that never start or stall, and retries deferred activations across organizations.
  • Results are redacted against every agent the turn could see. Each text is normalised (control characters and ANSI escapes stripped), masked whole, then cut to its cap. Only proofs made after the turn started count.
  • A result lands only over the draft it checked: if another chat saved over it before the landing, the landing fails with draft_changed and the old draft is not restored over that save.
  • A tool the rebuild added that no sub-goal names is a warning, not a failure (a sub-goal may check it by its effect).
  • A push observed while a credential change is still being checked or activated is built on that change, so it does not bring back the old value.

G. Drafts and Save — harness_versioning/drafts.py

  • With versioning on, chat edits, scenario edits and Extend write one draft per piece instead of changing the live environment.
  • Save turns every open draft into new versions and makes them the defaults (raising the same nudges as a manual move); Discard drops a draft. Out-of-date drafts are refused. Save and Discard wait while the chat or a rebuild is working.
  • Chat saves always write a new content-keyed archive; the in-place overwrite (in_place=True) in hosted_harness_gateway.py is removed.
  • Unsaved scenario rows are marked on the Scenarios tab and cannot be run; runs use the saved suite.

H. Run pins, assembly and bundle cache — harness_versioning/assembly.py, hosted_harness.py, hosted_harness_gateway.py, harness_scenarios.py

  • Each run job is pinned to the agent, environment and suite versions it runs, written once at creation.
  • A run looks up its sealed bundle by assembly_key in simulate_harness_assembled_bundle. A run of the defaults with no cached bundle runs as before, unless that default pair was made without a sealed bundle. A run pinned to other versions certifies its own bundle (when HARNESS_BUNDLE_CERTIFY_ENABLED is on) and fills the cache for the next run.
  • Run results (run_results_v3*.py, views/run_results_v3.py, views/run_test.py) return environment_version, suite_version, pinned_agent_version and agent_differs; null for runs from before versioning. run_test executions accept summary=true for a light list of ids, statuses and pins.

I. Run dialog and warnings — harness_versioning/run_warnings.py, workspace/run/RunDialog.jsx

  • Starting a run opens a dialog with agent, environment and suite pickers, defaults pre-selected. The run request accepts agent_version_id, environment_version_id, suite_version_id; an absent pick is the default.
  • run-plan/ previews the versions a run would use and its warnings, without writing. Warnings never block: variable_missing, provider_key_unavailable, credential_unavailable, nudge_open, suite_unproved, agent_differs, bundle_cache_failed, bundle_cache_miss.
  • A rerun with changed credentials returns the harness refusal (agent_preflight_failed, credential_blob_too_large, version_not_runnable) instead of a generic error.

J. Roll back — versions.py (move_default), EnvVersionPin.jsx, MakeDefaultConfirm.jsx

  • The environment and suite defaults can be moved to an older active version from the version pin menu or the Versions tab. The request sends expected_default_id; a stale one is refused. New runs use the new default; the previous version stays active.

K. Run compare — simulate/services/run_compare.py, run_results_v3_queries.py, runs/summary/RunCompare.jsx

  • compare/ pairs two finished runs of the same environment by scenario key and reports each scenario as improved, regressed, no change or inconclusive, from pass rates over trials (Newcombe 95% interval against a ±10-point margin). Eval scores are context only.
  • Scenarios only in one run and scenarios whose sub-goals changed are listed separately. The response names the versions each run used.
  • Not gated on the flag: runs from before versioning compare with null versions.
  • The compare UI (a picker on the runs list) is hidden for now (RUN_COMPARE_ENABLED); the compare/ endpoint is unchanged.

L. Legacy retirement

  • Removed the author_hosted_harness_job Temporal activity (temporal/activities/hosted_harness_gateway.py, tfc/temporal/common/registry.py).
  • Removed the unmounted fixture agent screens under workspace/agents/ (AgentsPanel, AgentCard, AddAgentDrawer, connect/*, PromoteDialog, DivergenceBanner, ...), the fixture versions (_fixtures/versions.js, _fixtures/envConfig.js), AgentRefreshBanner.jsx and the version slice of store/envState.js.
  • The create page (HarnessCreate.jsx, prepareSourceForBuild.js) sends credential values with the create request instead of minting vault refs first.

M. Frontend — frontend/src/api/simulate-environments/versions.js, compareRuns.js, nudgeActions.js; frontend/src/sections/simulate/environments/workspace/

  • Versions tab (versions/VersionsPanel.jsx, VersionDiff.jsx, VersionHeader.jsx): the three version lists, what changed in each, Make default. Shown only when env.versioning.enabled.
  • Agent versions (agents/AgentVersionsPanel.jsx, AgentCredentials.jsx, AgentSourceChange.jsx, PreflightStatus.jsx, RebuildAction.jsx): source, check status, upload new source or edit the prompt, change credential values (names shown, values never echoed).
  • buildEnvironment/console/NudgeCard.jsx: nudge card in the builder chat with Rebuild / Skip / Stop.
  • drafts/DraftBar.jsx: Save / Discard bar with a change count.
  • run/RunDialog.jsx, AgentDiffersTag.jsx: version pickers and warnings; pinned versions in RunDetail.jsx and SummaryTable.jsx.
  • Regenerated API contracts (api_contracts/openapi/swagger.json, frontend/src/generated/api-contracts/*, futureagi/mcp_server/catalog/tools.generated.json).

N. Review fixes (661b0e336, 4048014ff, 234fb0078; second round 038acb5b8…44ee13ade)

  • Save keeps the bundle in step with the saved pair (drafts.py _save_pair, _resealed). A Save used to carry the chat's or the default's sealed bundle even when it was sealed for another world or suite, so a run at the defaults replayed the old world. Now:
    • The chat's bundle is used only when the chat resealed it for exactly the saved pair. The harness stamps each resealed manifest with a digest of the authoring files it sealed from (sealed_from); the platform trusts the bundle only when that stamp matches the saved tree and the bundle's other environment files are unchanged (038acb5b8). A bundle from an earlier, unpromoted reseal is not trusted, and a chat turn whose reseal fails now ends failed and is not promoted.
    • Open: a contract.json-only edit still reuses the resealed bundle, since the reseal does not refresh what a build derives from the contract (# TODO: in _resealed).
    • The default's bundle is carried only when the environment version is unchanged and the suite's certify_hash is equal, since a run lays only overlay values (background noise, max turns, persona) over a replayed bundle.
    • Anything else is saved unsealed and certified by its first run. With certify off, Save is refused with draft_not_runnable. An environment with no agent to certify for is told to make the change in chat.
  • World data is identified by world.sqlite. generic-harness/world-ir.json stays out of the environment hash: it embeds the agent's source digest, and its row ids and value coercion come from the agent's source model. Tests pin that a world.sqlite change moves the hash and makes a new version, and that a world-ir.json-only change does not. The harness side keeps the IR derived from world.sqlite (agent-learning-kit PR).
  • All of generic-harness/ is out of environment identity (32df8c74c). The harness keeps only derived records and caches there; its runtime-validation cache (setup-checks-passed.json) was hashed and minted a version on every validation pass. That cache and runtime-plan-repair-* are now sealed; provider-import-profile.json and source-data-review-early-error.txt are no longer stored. Environments versioned on this branch get one extra environment version on their first save after this change.
  • Rebuild landing (9e23648e2): a landing fails with draft_changed when another chat saved over the rebuild's draft first, including a save of the default.
  • Pending credential change and a push (bee5835ae): the push version is built on the outstanding requested version.
  • Report masking (0f7848ad2): texts are normalised, masked, then cut, so no prefix of a credential survives.
  • Backfill command (fa3f923c4): INSTALLATION.md documents the operator form for Compose and Helm.
  • New-tool check (2a7579508): a tool no sub-goal names is a warning, not a failure, on both sides.
  • One Wilson helper (44ee13ade): run_compare uses run_reliability_v3.wilson.
  • The harness side of this round: a failed reseal fails the turn, a repair keeps absent values absent so the source's defaults apply, a repair world.sqlite can't hold fails the attempt, and a rebuild may edit world checks uncited when the agent has no source (agent-learning-kit PR).

O. Supporting changes

  • Sentry (tfc/logging/sentry.py) and telemetry (tfc/telemetry/middleware.py) scrub environment_values and credential_values; the secret-values/, secret-files/ and webhook paths are excluded from early body reads.
  • simulate/apps.py loads simulate/signals.py on startup; the GitHub poller lives in simulate/tasks/harness_agent_changes.py.
  • Docs: INSTALLATION.md (backfill then flag), deploy/env-reference.toml (the three flags), futureagi/.env.example (GITHUB_APP_WEBHOOK_SECRET).

New endpoints (on harness-environments/{id}/, views/harness_environment.py)

Method Path Purpose
GET versions/ Every agent, environment and suite version
GET versions/{piece}/{id}/diff/ What one version changed (optional against)
POST versions/{piece}/{id}/default/ Make a version the default (roll back)
POST versions/agent/ New agent version from uploaded source or a prompt edit
POST versions/agent/{id}/discard/, versions/agent/{id}/retry/ Discard or re-check an agent version
GET versions/suite/{id}/members/ Scenarios in a suite version
GET / POST / DELETE drafts/, drafts/save/, drafts/{piece}/ List, save, discard unsaved edits
GET / POST nudges/, nudges/{id}/answer/, nudges/{id}/stop/ List, answer, stop nudges
GET run-plan/ Versions and warnings for a run of these picks
GET compare/ Compare two runs of the environment
POST api/harness/github/webhook/ Signed GitHub push webhook (not on the viewset)

2) Why the changes were done

  • Product/UX: Environments were edited in place, so a run could not be tied to what it ran against, a bad edit could not be undone, and agent changes went unnoticed. Versions, run pins and roll back make results reproducible, and nudges keep an environment in step with its agent.
  • Technical: Versions are immutable, content-addressed archives in the existing object store, indexed in Postgres. Identical content reuses a version, and default moves are conditional updates, so concurrent writes conflict instead of overwriting.
  • Architecture: Agent runtime data lives on a side table (simulate_harness_agent_version_runtime) instead of new columns on the agent tables. Readers decide by data (pins present or not), so pre-versioning environments and runs keep working.

3) Tests written + scenarios each covers

Backend (futureagi/simulate/tests/):

Models, hashing, archives, recording

  • test_harness_versioning_models.py — table constraints and defaults
  • test_harness_versioning_hashing.py — environment, suite, agent and assembly hashes; world.sqlite is hashed, world-ir.json is not
  • test_harness_versioning_archives.py — split, compose and store archives; world.sqlite lands in the environment piece
  • test_harness_versioning_record.py — recording versions on authoring, reuse on identical content; a world.sqlite change makes a new environment version
  • test_harness_versioning_history.py — run results read the pinned suite's content
  • test_harness_amend_archive.py, test_harness_scenario_amend.py (additions) — scenario edits as versions

Backfill

  • test_harness_versioning_backfill.py — first versions and run pins for existing environments, idempotency
  • test_harness_versioning_backfill_check.py — --check reports without writing

Agent versions and changes

  • test_harness_agent_versions.py — credential sets, preflight, activation, discard and retry
  • test_harness_agent_creation.py — create with credential values, one agent version per environment
  • test_harness_agent_provision.py — provisioning the pinned agent version in the sandbox
  • test_harness_agent_runs.py — runs and reruns with the pinned agent version
  • test_harness_agent_source_api.py — upload source and prompt edit endpoints
  • test_harness_agent_changes.py — new versions from pushes, uploads and edits; per-environment holds
  • test_harness_agent_receiver.py — the AgentVersion save receiver
  • test_harness_github.py — unauthenticated GitHub reads and the shared budget
  • test_harness_github_webhook.py — webhook signature, size and throttle checks

Nudges, drafts, rebuild

  • test_harness_nudges.py — raising, de-duplicating, redacting and closing nudges
  • test_harness_versioning_drafts.py — draft writes, staleness, Save and Discard; TestPairLadder: which bundle a Save keeps (chat world edit then a Scenarios-tab edit, a world edit whose reseal failed, backfilled and run-certified defaults, an earlier turn's bundle, overlay-only edits, an environment with no agent)
  • test_harness_versioning_saves.py — every save of a versioned environment records versions, flag or no flag
  • test_harness_versioning_rebuild.py — rebuild turn: land, nothing to change, fail, stop, draft restore; a chat save before the landing is not landed
  • test_harness_versioning_reseal.py — which chat bundles count as resealed (stamp for this tree, an earlier world, unstamped, unreadable draft)
  • test_harness_versioning_rebuild_report.py — report texts are masked before they are cut

Runs, roll back, compare, API

  • test_harness_versioning_runs.py — run pins
  • test_harness_versioning_picks.py — run picks, run-plan/ and warnings
  • test_harness_versioning_assembly.py — assembly and the bundle cache
  • test_harness_versioning_rollback.py — default moves and conflicts
  • test_harness_run_compare.py — scenario verdicts and the compare/ endpoint
  • test_harness_versioning_api.py — the versions endpoints and the flag gate

Other

  • Additions to test_agent_definition_services.py, test_harness_provider.py, test_hosted_harness_conversation.py, test_hosted_harness_eval_selection.py
  • tfc/temporal/common/tests/test_registry.py, tfc/temporal/schedules/tests/test_register_schedules.py — activity registration and the two new schedules
  • tfc/tests/test_sentry_filtering.py, tfc/deployment_telemetry/tests/test_privacy.py — credential values are scrubbed
  • model_hub/tests/test_clickhouse_cache_warm.py — startup guard for the backfill command

Frontend (frontend/src/; workspace/ and buildEnvironment/ are under sections/simulate/environments/):

  • api/simulate-environments/__tests__/versions.test.jsx, compareRuns.test.jsx, scenarioSelection.test.js — new API hooks
  • workspace/versions/__tests__/versionsPanel.test.jsx — Versions tab, diffs, Make default
  • workspace/agents/__tests__/agentVersionsPanel.test.jsx, agentCredentials.test.jsx, agentSourceChange.test.jsx — agent versions, credentials, source changes
  • buildEnvironment/console/__tests__/NudgeCard.test.jsx, NudgeCardRebuild.test.jsx — nudge card and rebuild states
  • workspace/drafts/__tests__/DraftBar.test.jsx, workspace/scenarios/__tests__/unsavedScenarios.test.jsx — Save / Discard and unsaved rows
  • workspace/run/__tests__/RunDialog.test.jsx, workspace/__tests__/EnvironmentWorkspace.runDialog.test.jsx — version picks and warnings
  • Updated suites for the changed screens and hooks (RunAnalytics, RunDetail, RunsSummary, HarnessCreate, overview, header, panels, useEnvironmentsStore and others in the diff); removed the tests of the deleted fixture agent screens

Full backend and frontend suites were run clean before each step was pushed.


4) How to run / test

Backend tests

cd futureagi
bin/test simulate/tests/ -k "harness_versioning or harness_agent or harness_nudges or harness_github or harness_run_compare"
bin/test tfc/temporal tfc/tests/test_sentry_filtering.py tfc/deployment_telemetry

Frontend tests

cd frontend
yarn test:run src/api/simulate-environments src/sections/simulate/environments src/pages/dashboard/harness

Contract verification

cd frontend && yarn contracts:check

Rollout

  1. Ship the companion agent-learning-kit PR (feat(harness): add scenario proofs, certify-only sealing and nudge rebuild turns agent-learning-kit#180) first and build the sandbox template from it. Then deploy this PR; its worker and API must ship together (the author_hosted_harness_job activity is removed).
  2. python manage.py migrate (0098–0102), then python manage.py register_temporal_schedules (adds poll_harness_github_heads and tick_harness_nudges).
  3. python manage.py harness_versions_backfill --check; fix every error it reports.
  4. Run the write as a one-off operator process: docker compose exec -e SERVICE_TYPE=bootstrap -e STARTUP_DB_MUTATION_MODE=operator -e NO_STARTUP_DB_MUTATIONS=false backend python manage.py harness_versions_backfill (app in Standalone). On Helm: kubectl -n futureagi exec deploy/futureagi-backend -c backend -- env SERVICE_TYPE=bootstrap STARTUP_DB_MUTATION_MODE=operator NO_STARTUP_DB_MUTATIONS=false python manage.py harness_versions_backfill.
  5. Rebuild and verify the release sandbox template (a certify run of an existing environment completes), then set HARNESS_VERSIONING_ENABLED=true and HARNESS_BUNDLE_CERTIFY_ENABLED=true together.
  6. Set HARNESS_VERSIONING_NUDGES_ENABLED=true.
  7. Optional: set GITHUB_APP_WEBHOOK_SECRET to accept push webhooks; without it, the poller still picks up pushes.
Flag Default On
HARNESS_VERSIONING_ENABLED off Records versions for new and edited environments, pins runs, shows the versioning UI. Off: new environments are created unversioned; environments that already have versions keep them.
HARNESS_VERSIONING_NUDGES_ENABLED off Raises nudges and offers rebuilds; only with versioning on
HARNESS_BUNDLE_CERTIFY_ENABLED off A run of a combination with no cached bundle certifies its own. Off: defaults run as before; a non-default pick with no ready bundle is refused, as is a default pair with no sealed bundle, and a Save that needs a new bundle is refused. Turn it on with versioning

UI steps (manual)

  1. Start the stack, run the backfill (--check, then the operator form in Rollout step 4), turn the three flags on, and log in at http://localhost:3031.
  2. Open an environment → Versions tab: check the agent, environment and scenario lists and a version's changes.
  3. Run simulation: the dialog pre-selects the defaults; pick an older scenario version and read the warnings.
  4. Version pin → Roll back to an older version; start a new run and check it uses that version.
  5. Overview → Manage versions → Change values: a new agent version is checked and becomes the default.
  6. Edit in the builder chat: the Save / Discard bar appears; Save creates new versions.

5) Screenshots / recordings

Test output

UI testing


6) Edge cases & considerations

  • Flag off: versions endpoints return 404 (require_versioning), the Versions tab and run dialog are hidden; compare/ still works.
  • Runs from before versioning: pins are null in run responses and compare; runs whose revision does not match the backfilled version are left unpinned.
  • Concurrent default moves: conditional update with expected_default_id; the loser gets a conflict.
  • Identical content: an environment or suite version with the same hash is reused instead of adding a new one.
  • Credential values: stored encrypted on the agent version only; never returned, logged, put in nudge facts or diffs; scrubbed from Sentry and telemetry. Rebuild report texts are masked whole before they are cut, so no prefix of a value survives.
  • Credential change still pending when a push arrives: the push's agent version is built on the pending change, so the rotated value is kept whichever activates first.
  • Private GitHub repositories: reads are unauthenticated, so nudges list no file changes for them (private_repository).
  • Rebuild failure or stop: the person's unsaved draft is restored; Save, Discard and default moves wait while a rebuild runs. A run-chat save between the rebuild's checks and its landing is not landed over: the landing fails with draft_changed, and the restore does not bring the old draft back over that save.
  • Unsaved scenarios: cannot be run; runs use the saved suite.
  • Save after an edit the bundle doesn't carry: a Scenarios-tab edit beyond overlay values, or a chat edit whose bundle isn't stamped for the saved tree (no reseal, a failed reseal, or an earlier reseal's bundle), saves an unsealed pair that the next run certifies. Exception: a contract.json-only chat edit still reuses the resealed bundle (open, see below).
  • First save after upgrading an environment versioned on this branch: one extra environment version, because generic-harness/ left environment identity.
  • Busy environments during backfill: the backfill can be run again later.

Open items

  • A contract.json-only chat edit still reuses the resealed bundle; the reseal does not refresh what a build derives from the contract (# TODO: in drafts._resealed).
  • The harness reseal copies the authoring contract.json over the bundle's. Whether that can drop runtime.interface from a sealed contract is not yet checked.
  • In local E2E, certify runs on a sandbox template built from the latest agent-learning-kit head timed out on one scenario's ready check, while the same pairs certified and ran on the previous template. This points at the template build: rebuild and verify the release template before enabling versioning.
  • Merging latest dev, folding migrations 0098–0102 into one after dev's latest, and whether the author_hosted_harness_job removal moves to its own PR are pending and will be handled in a follow-up push.

7) Pre-existing issues (NOT introduced by this PR)

None known.


8) Architectural / important decisions

  • Content-addressed archives, no git: versions are immutable archives keyed by hash; Postgres rows index them.
  • Side table for agent runtime data: no new columns on the agent tables; credential sets live on the agent version.
  • Backfill as a command, not a migration: a backfill problem cannot block other migrations, and --check runs first.
  • Readers decide by data, not the flag: an environment with versions keeps recording them even if the flag is turned off.
  • Warnings never block a run: the dialog explains risk; the person decides.
  • Drafts until Save: builder edits no longer change the live environment, so runs always use saved versions.

Backward compatibility

  • All three flags default off; with versioning off, environments are created unversioned and existing paths run unchanged.
  • Existing platform-vault secret refs keep resolving.
  • secret-values/ changed: it now requires environment_id and returns 202 with the new agent_version instead of 201 with secret_refs. In this repo, the create page now sends credential_values with the create request, and changeCredentials in versions.js sends environment_id.
  • The author_hosted_harness_job activity is removed; deploy workers with the API.

9) End-to-end test on the local stack

  1. Create an environment from a public GitHub repo watching main. The poller sees no change on the built commit, the Run dialog shows no warnings, and the baseline run completes.
  2. Push code. A new agent version passes preflight and becomes the default, and the card lists the changed files.
  3. Rebuild, then Stop at once, then Rebuild again. The environment lands as a new version, and a scenario card follows.
  4. Push an empty commit. A new agent version is created and no card is raised.
  5. Push a docs/tests-only change. The rebuild reports that the environment already fits the new version.
  6. A prompt change shows as a file diff on the card.
  7. Leave a card unanswered. The Run dialog warns that the agent differs and that an update is waiting.
  8. A newer push replaces a pending card, and the new card's diff includes the replaced change.
  9. Skip a card ("Skipped. The current version stays."). The Run dialog shows the skipped wording.
  10. Push new tools (booking status and cancel). The environment rebuild lands in one Rebuild, with every tool owned by the agent's runtime.
  11. Edit a scenario before answering a card. The edit is kept as an unsaved draft.
  12. Rename a tool and its arguments (geocode_address → search_places). The environment and scenario rebuilds land, and every scenario key stays the same.
  13. Push a seed-only change, then push again while that rebuild runs. The older card ends as replaced (stopped by a newer one), and the newer card carries both seed changes.
  14. Skip a scenario card. The Run dialog warns that the suite is unproved.
  15. Add a credential. A new agent version is created, the card names the variable, and no value is shown anywhere.
  16. Rotate a credential's value. A new agent version and card are created, and no value is shown.
  17. Push a change that needs the new variable (token auth). Preflight passes and the environment rebuild lands.
  18. Push new tables and tools (saved places, recent trips). The environment adds a sub-goal for each new tool, and the suite adds "take me home" and "same place as last time" scenarios.
  19. Push two versions close together. The later card replaces the earlier one.
  20. Run with picks (an older agent against the current environment). The warnings show and the run starts.
  21. Roll back the agent version. A "rolled back" card is raised and can be skipped.
  22. Roll back the environment version.
  23. Run a rebuilt environment with a new agent version. All 12/12 calls complete.
  24. Compare that run with the baseline. Pairs are returned, including changed criteria and new scenarios.
  25. Save a stale draft after the default moved. It is refused with 409 draft_stale.
  26. Rebuild while a stale draft exists. The draft is set aside and the card says so.
  27. Edit in the builder chat while a rebuild is running. It is refused with 409 rebuild_running. A run-chat save between the rebuild's checks and its landing is not refused; the landing then fails with draft_changed (covered by tests only).
  28. Chat world edit (Dana's Uber Cash 5 → 7.25), then a Scenarios-tab edit, then one Save. The pair is saved unsealed; run 03d7d899 certifies and its bundle seeds 7.25.
  29. Chat-only world edit (7.25 → 7.50) and Save. Run 86ef4ef6 certifies and its bundle seeds 7.50.
  30. Chat world edit (60 → 61.50) on an environment whose stored world-ir.json still holds 60. Run f428502f certifies from world.sqlite and seeds 61.50.
  31. First chat save after generic-harness/ left environment identity: environment v13 → v14 with no world edit; the change summary counts two world files changed.
  32. Chat world edit saved as environment v15 and suite v6, unsealed (the chat's bundle is not reused). Run 59572bb5 certifies the pair with --certify-only and completes 1/1 calls.
  33. Control run of the defaults (run eb3aba24): the cached bundle replays and the run completes.
  34. Backfill: the plain write command is refused by the startup guard; --check reports 6 environments current, 0 errors; the operator form runs (current 6).
  35. Not run live: credential rotation plus a push, and the new-tool warning (the push to the test repo was blocked locally), and the rebuild landing race (not reachable through the API). These are covered by tests only.

Checklist

  • My code follows the style guide
  • I've added tests that prove my fix is effective or that my feature works
  • bin/test passes locally (backend)
  • yarn test:run passes locally (frontend)
  • yarn contracts:check passes if API surface changed
  • I've updated the documentation where relevant
  • No hardcoded secrets, URLs, or PII
  • I've signed the CLA
  • PR title follows Conventional Commits
  • Linear issue is linked and updated with status

…oning

# Conflicts:
#	api_contracts/openapi/management-api-contract-debt.generated.json
#	api_contracts/openapi/runtime-management-api-contract-debt.generated.json
#	frontend/src/api/contracts/api-surface.generated.js
#	frontend/src/api/contracts/openapi-contract.generated.js
#	frontend/src/sections/simulate/environments/workspace/workspace.constants.js
#	futureagi/mcp_server/catalog/tools.generated.json
#	futureagi/simulate/services/hosted_harness_conversation.py
- The create request carries the agent's variable values; they are kept only in the agent version.
- Each environment gets its own agent version, pinned for its runs and builds.
- Environments on the same provider assistant share one agent definition.
- The build's resolved commit is written onto the environment's own version.
- The launcher sends the values with the create request instead of minting vault refs.
- Watch each environment's GitHub source through a signed webhook and a scheduled poller, with per-owner, per-org and hourly budgets.
- A new commit, an uploaded archive or a prompt edit creates an agent version that is checked, then offered to each environment that uses it.
- Discards and roll backs hold per environment; the first sight of an existing environment records its head instead of creating a version.
- Sources are tracked by GitHub repository id; private repositories are refused at creation.
- The versions panel shows each agent's source and lets people upload new source or edit the prompt.
- New compare endpoint pairs scenarios by key and reports each one as improved, regressed, no change or inconclusive from the pass rates over trials.
- Scenarios only in one run, and scenarios whose sub-goals changed, are listed separately.
- The response names the agent, environment and scenario versions each run used.
- Run analytics gets a picker to choose the run to compare against.
…nges

- A new default agent version, or a newer version an environment can pick, raises a nudge card in the builder chat with what changed: files, prompt, settings and variable names, never values.
- Moving the environment default raises a scenarios nudge; nudges are re-checked when a build or extend completes and closed when the environment is deleted.
- One environment nudge is pending at a time: the default's own card wins, and only the newest version is offered.
- GitHub compares use their own capped share of the hourly budget, and patches are redacted against the agent's credential values.
- The nudges list and Skip endpoints, the chat card, the Agents panel action and the unread dot; the old fixture banners are removed.
- With versioning on, chat edits, scenario edits and Extend write one draft per piece instead of changing the live environment.
- One Save turns every unsaved piece into new versions and makes them the defaults, which raises the same nudges as a manual move; Discard drops them.
- Out-of-date drafts are refused and replaced by the next edit; Save and Discard wait while the chat is working, but not on an unanswered question.
- The Scenarios tab marks unsaved rows, which can't be run until saved, and runs use the saved suite; the Contract and Overview views stay on the live version.
- Chat checkpoints are packed deterministically so the same checkpoint is not saved twice.
- Remove the unused authoring activity, the unmounted fixture agent screens, the fixture versions and the environment state's version slice.
- The old create page sends credential values with the create request; secret-values/ now always needs an environment and creates a new agent version.
- Chat saves always write a new content-keyed archive instead of overwriting in place.
- Document the three versioning flags (all off by default) and the backfill-then-flag upgrade order.
@github-actions github-actions Bot added the docs label Oct 17, 2026
- The e2e stack turns on versioning, nudges and certify, blanks sandbox-provider keys, and sets a test encryption key.
- Seeds create a backfilled environment, apply edits and plant a finished run, scoped to the flow's org and workspace.
- Flows SIM-E2E-001 to 004 cover the versions tab, run picks with warnings, roll back, and a credential change that makes a new agent version.
- Seeding refuses to run when a real sandbox-provider key reaches the stack.
- Rebuild queues a builder turn with a names-only briefing, then lands its result as new environment or scenario versions, reports nothing to change, or fails and puts the person's unsaved edits back.
- Unsaved edits are held aside during the turn and restored on every outcome that doesn't land; Save, Discard and default moves wait while a rebuild runs.
- Stop works from the card, the agents panel and the composer; a tick closes rebuilds that never start or stall, and retries deferred activations fairly across organizations.
- Results are redacted against every agent the turn could see, and only proofs made after the turn started count.
The e2e harness, seeds and flows for environment versioning run locally only and are not part of this branch.
@JayaSurya-27 JayaSurya-27 changed the title Feat/environment versioning feat(simulate): version agents, environments and scenario suites and pin runs to them Oct 20, 2026
@JayaSurya-27
JayaSurya-27 marked this pull request as ready for review October 20, 2026 05:25

@hadarishav hadarishav left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for three confirmed bugs in the platform implementation.

The review and reproductions used b0b663241be69de5e0a6f3e6129289bf30c3e49c. Before posting, I checked the current head 183622f1dc985b9a4cbb42cf4d25b1b9d54638bc: all three files below are unchanged. This is not a complete review of the newer commits or all 274 changed files.

  1. P1: Save can publish a new environment version that still executes the old world. drafts._save_pair accepts a carried bundle and _composed_pair marks the new pair sealed without establishing that the bundle contains the edits. The gateway copies that bundle directly for simulation-only execution. A database-backed reproduction saved changed world bytes, created a new environment version, and left the executable bundle containing the old world. Require a bundle certified for the exact saved pair; otherwise prepare it through --certify-only. Checking the draft hashes alone is insufficient when workspace_sealed contains a carried bundle.

  2. P1: Real world-data changes are excluded from environment identity. hashing.py excludes all of generic-harness/world-ir.json, although ALK preferentially uses it as runtime seed data when present. Two valid worlds with balances 10 versus 999 produce different packaged runtime data but the same environment hash. A database-backed record_draft reproduction then produces no environment draft and retains the previous archive. Hash the semantic world contents, excluding only metadata that must not affect identity.

  3. P2: Equal observed pass rates incorrectly imply “No meaningful change.” run_compare.verdict returns this label before checking uncertainty whenever the rates match. For 1/1 versus 1/1, the actual function reports that label with an interval of approximately -79 to +79 percentage points. PRD §4.10.7, lines 552–555 requires the entire interval inside the equivalence margin. Remove the shortcut and update the test that currently enforces it; wide intervals should remain inconclusive.

Verification on the reviewed commit: 1,301 existing backend tests passed, plus two database-backed reproductions confirming the integrity bugs. Four phone-agent tests needed a test-only outbound caller ID and passed on rerun. Passing tests do not resolve the findings above.

Browser and live E2B/provider execution were not checked. Review remains incomplete; fixing these findings is not an approval of the whole PR.

@hadarishav

Copy link
Copy Markdown
Contributor

The implementation is a substantial versioning foundation, but fixing the reported bugs does not complete the agreed plan. Please resolve the scope of each item below before presenting this work as complete.

For each row, either implement the requirement or explicitly defer it in both this PR description and the relevant Linear ticket, with Chintan's alignment recorded. A deferral should identify what is excluded, the follow-up ticket and owner, and the user-visible limitation. Please distinguish the current milestone from the full feature, including what “Fixed TH-7948” covers. TH-8656 and TH-8657 were still Todo when reviewed; these are scope decisions, not a demand to silently absorb every related ticket into this PR.

The references below use the exact PRD revision reviewed. Linear references use requirement numbers because tickets have no stable line numbers. The table describes gaps identified at reviewed head b0b6632; please map any subsequent implementation to its commit and tests.

Pending work What remains Exact PRD reference Ticket reference
Change classification Additive changes retain the displayed E version with stored revisions; breaking changes create a new E; core changes fork. §4.10.2, lines 419–424 TH-8656, Requirements 1 and 6
Per-scenario versions Patch/minor/major semantics determining result reuse, re-grading or re-running. §4.10.3, lines 437–439 TH-8656, Requirements 2–3
Not-applicable scenarios Keep affected scenarios in S, skip execution/charging where inapplicable, and restore applicability when the capability returns. Dropping them from a suite is insufficient. §4.10.5, line 500; AC-13.7, line 1613 PRD is the clearest explicit requirement
Complete comparison records Record scenario/world dependencies, sub-goal and eval versions, simulator model, run limits, fidelity, trials and seed. §4.10.3, line 430 TH-8657, Requirement 2
Full comparison behavior Change summary, cross-environment pairing, and exact/re-graded/rechecked/changed classifications. §4.10.7, lines 534–549 TH-8657, Requirements 1 and 4, User journey 2–4
Pre-run comparison preview Expected comparability and uncertainty, with priced options before starting. §4.10.7, lines 559–565 TH-8657, User journey 1, Requirements 6 and 10
Maintaining comparison coverage Frozen anchors, a runnable reference agent, and bridge/backfill runs with availability checks. §4.10.8, lines 571–579 TH-8657, Requirements 7 and 10
Hosted-provider change detection and sync Detect provider changes and re-read changed setup into an agent version; manual sync is separate work. AC-13.1 and AC-13.3, lines 1607–1609 TH-7948, User journey 1; TH-8534, Done when, first bullet

Please split this work into stacked PRs for appropriate review. The reviewed platform diff contains 274 files, 80,680 additions and 6,174 deletions. The review so far covered selected critical paths, not every changed line. Four confirmed bugs across the pair are not evidence that only four exist.

A suggested stack is:

  1. Version models, hashing, archives, migrations and backfill.
  2. Agent versions, credentials, preflight and change detection.
  3. Drafts, Save/Discard and default moves.
  4. ALK certification/proofs and platform run assembly/pinning, with cross-repo dependencies explicit.
  5. Nudges and rebuild lifecycle.
  6. Versioning UI and run comparison.

Adjust the boundaries to preserve coherent, testable behavior. Each PR should show only its incremental diff against its predecessor, map to the agreed requirements, include focused verification, and document deployment dependencies. Generated contracts and tests should accompany the behavior they cover. Please provide the stack order and a coverage checklist so reviewers can track what has actually been reviewed.

Current verdict remains request changes and review incomplete. Fixing the listed bugs alone is not approval of the full implementation. Browser and live E2B/provider flows remain unverified.

A Save could make the default a pair whose carried or chat bundle was sealed
for another world or suite, so runs replayed the old world. The chat's bundle
is used only when it was resealed for exactly the saved pair (decided by the
bundle manifest, not the object key), the default's bundle is carried only
when the environment is unchanged and the suite's certify hash is equal, and
anything else is left for the first run to certify. An environment without an
agent to certify for is told to make the change in chat.
world-ir.json stays out of the environment hash; the tests pin that a
world.sqlite change moves the hash and that world.sqlite lands in the
environment piece.
A validation repair is now written into world.sqlite, so it versions the
environment; a world-ir.json-only change keeps the version.
@JayaSurya-27

JayaSurya-27 commented Oct 22, 2026 •

Copy link
Copy Markdown
Member Author

Thanks for the review and the reproductions. Pushed to feat/environment-versioning, now at 234fb0078; the ALK side is at 2e3a4b87 in future-agi/agent-learning-kit#180.

1. Save publishing the old world: fixed in 661b0e336.

drafts._save_pair now picks the pair's bundle with these rules:

  • The chat's sealed bundle is used only when the chat resealed it for exactly the saved pair: same environment hash and same suite hash. "Resealed" now means ALK stamped the bundle's manifest with a digest of the authoring files it sealed from, that stamp matches the saved tree, and the bundle's other environment files are unchanged (038acb5b8, ALK d3d153dc). So a turn whose reseal failed, a bundle from an earlier unpromoted reseal, an earlier turn's bundle still held by its draft, or the default's own bundle under a backfilled or run-certified key is not taken as resealed (_resealed). A chat turn whose reseal fails now also ends failed, so it is not promoted.
  • The default pair's sealed bundle is carried only when the environment version is unchanged and the suite's certify_hash is equal. A run lays only the overlay values (background noise, max turns, persona fields) over a replayed bundle, so any other suite edit changes what the bundle must hold.
  • Anything else goes to the existing ladder. A bundle already held for exactly that pair, kept from when it was the default or cached by an earlier run, is reused. Otherwise the pair is saved unsealed and its first run certifies it with --certify-only. With HARNESS_BUNDLE_CERTIFY_ENABLED off, a Save that needs a new bundle is refused with draft_not_runnable.
  • An environment with no agent to certify for is told to make the change in chat instead.

Tests, all in test_harness_versioning_drafts.py::TestPairLadder:

  • a chat world edit followed by a Scenarios-tab edit;
  • a world edit whose bundle was not resealed;
  • a not-resealed bundle carried into an added scenario;
  • a non-overlay Scenarios-tab edit, and an added overlay field;
  • the default's bundle under a backfilled or a run-certified key;
  • an earlier turn's bundle reused for a later world edit, including when the earlier record can't be read;
  • saving only the suite while both drafts are open;
  • an overlay-only edit, which still carries;
  • the chat's resealed bundle, which is still used.

The fixtures for chat saves and rebuild landings in the drafts, record, rebuild and runs tests now model the real chat reseal; previously they carried the default's manifest unchanged. The rollback tests now save their Scenarios-tab edit with bundle preparation on.

Two gaps remain, both marked in the code or limited to old data:

  • A contract.json-only edit still reuses the resealed bundle, though the reseal does not refresh what a build derives from the contract. This is marked TODO in _resealed.
  • Pairs saved by the old carry keep their recorded bundle until they are saved again. Versioning is off on dev, so this only affects environments built from this branch.

Local E2E on the full stack, with a sandbox template built from the ALK fix branch:

  • Chat world edit (the turn also adjusted one scenario's ready check), then a Scenarios-tab edit, then one Save: Dana's Uber Cash went from 5 to 7.25. Environment v10 and suite v5 were saved unsealed. Run 03d7d899 missed the bundle cache and certified; its bundle seeds 7.25. 1/1 calls completed.
  • Chat-only world edit and Save: 7.25 to 7.50, environment v11. Run 86ef4ef6 certified; its bundle seeds 7.50. 1/1 calls completed. The rule that uses the chat's resealed bundle couldn't be reached live, because this environment's default pair was unsealed; TestPairLadder covers it.

2. World data and the environment hash: no change to excluding generic-harness/world-ir.json.

That file embeds the fingerprint of the agent's source model, and its row ids and value coercion depend on the agent's source model. Hashing it would tie environment identity to the agent version. The world data is identified by the hashed world files (world.sqlite, store.json, collections.json, schema.sql).

The fixes below keep world-ir.json from diverging from world.sqlite:

  • The IR is adopted only as world.sqlite's exact import (ALK 0cd6fd42). A compile uses world-ir.json only when it is exactly what world.sqlite imports to; otherwise it seeds world.sqlite.
  • Validation repairs are written into world.sqlite (ALK 8b74e2c9). In a tree that holds world.sqlite, a repair now changes the environment hash. The re-import becomes the IR. A new row keeps absent values absent, so the source's defaults apply rather than SQLite's (ALK 8df36a4f). A repair world.sqlite can't hold fails that repair attempt with the original validation error (ALK 09e7bc68).
  • Certified bundles are reused only while they seed world.sqlite (ALK 2e3a4b87). A certified bundle is reused only while its seed is the authoring world.sqlite or its exact import, and a reseal that rewrites the IR records its new fingerprint.
  • Platform tests (4048014ff, 234fb0078) pin the hash side. A world.sqlite change moves the hash and creates a new environment version holding it. A world-ir.json-only change keeps the version; that IR is no longer adopted, so it can't seed.

What the code now guarantees: for an environment whose authoring tree holds world.sqlite, every bundle the release produces on the generic pipeline (the only one the platform dispatches) seeds either the world.sqlite it was sealed from or a World IR that is exactly what that world.sqlite imports to, under the source model in the same bundle. That covers build, repair validation, certify, extend, and the reseal at the end of a chat or rebuild turn. A Save uses a chat's bundle only when its stamp matches the saved tree (038acb5b8), so a saved version does not run an earlier reseal's world.

Your reproduction (a World IR of 10 vs 999 beside the same world.sqlite) now seeds world.sqlite in both cases, so the IR difference no longer reaches the runtime.

Four exceptions remain:

  • Bundles sealed before this release replay as stored, on default-pair runs and cache hits, until a chat save, a certify or a rebuild replaces them.
  • An environment whose validation repair lives only in its IR fails certification until it is rebuilt. It never runs the unrepaired world silently.
  • A tree with no world.sqlite keeps the World IR as its world, and that IR is still not hashed. Hosted builds write world.sqlite, and we found no hosted path that produces such a tree.
  • A contract.json-only chat edit still reuses the resealed bundle (TODO in _resealed).

ALK tests for this guarantee:

  • test_the_world_a_bundle_seeds_is_always_the_hashed_world_sqlite covers a repair, reseals with and without a world edit, and certify-only's compile.
  • test_a_compile_seeds_world_sqlite_over_a_world_ir_it_does_not_import_to
  • test_a_reseal_keeps_a_validation_repair_of_the_world
  • test_a_certified_bundle_is_reused_only_while_it_seeds_world_sqlite

Local E2E: on an environment whose stored world-ir.json still held Dana at 60, a chat edit set world.sqlite to 61.50. Run f428502f certified from world.sqlite and seeds 61.50.

3. Compare verdict: deferred with compare as a whole. The compare UI is hidden in this PR (183622f1d, RUN_COMPARE_ENABLED = false). The compare/ endpoint still answers and still returns that label for equal rates. The verdict fix will land with the compare work in TH-8657, which is on hold until the designs and flows are handed over.

Updated after the second review round: a chat bundle is trusted only when ALK's stamp matches the saved tree and a failed reseal fails the turn (038acb5b8, ALK d3d153dc); repairs keep absent values absent and an unstorable repair fails the attempt (ALK 8df36a4f, 09e7bc68); a contract.json-only edit is still open.

@JayaSurya-27

JayaSurya-27 commented Oct 22, 2026 •

Copy link
Copy Markdown
Member Author

Thanks for mapping the scope this precisely. Here is where each item stands. The PR description now says "Part of TH-7948"; this PR doesn't close it.

Pending work Decision User-visible limitation now Ticket Owner Priority
Change classification and the core fork Additive/breaking classification is in TH-8656. The core fork was cut from this version for the deadline. Every saved environment edit creates a new environment version TH-8656 Jayasurya Putti Low
Per-scenario versions We version the suite as a whole by design. This was discussed and agreed with Chintan. Results are compared per scenario key inside a suite version — — —
Not-applicable scenarios Dropped scenarios will keep their reason, and the harness restores them with their keys when the capability returns, with no manual restore. They stay out of the current suite meanwhile, which departs from §4.10.5. A scenario that needs a removed capability leaves the current suite TH-8754 Jayasurya Putti Medium
Complete comparison records On hold until the designs and flows are handed over. Runs record their pinned versions only TH-8657 Jayasurya Putti Medium
Full comparison behaviour Same as above. No change summary or cross-environment pairing; the compare UI is hidden TH-8657 Jayasurya Putti Medium
Pre-run comparison preview Same as above. The run dialog shows warnings, not a comparability preview TH-8657 Jayasurya Putti Medium
Maintaining comparison coverage Same as above. No anchors or bridge runs TH-8657 Jayasurya Putti Medium
PRD §4.10.6: whether a changed prompt rule is a fix or a policy change Tracked separately. Prompt changes show as file diffs on the nudge card TH-8753 Jayasurya Putti Low
Hosted-provider change detection and sync Re-reading a provider's setup is TH-8534; dev's store_agent already writes AgentVersionSetup.setup_hash (dev 37abd769bb). Detecting changes by comparing setup_hash is the remaining TH-7948 work, after dev is merged. Vapi and Retell agents get no new agent version when their provider setup changes TH-8534 Dileep Kumar High

Compare as a whole is hidden in this PR (183622f1d), and its verdict fix moves to TH-8657.

Stacked PRs: we are keeping this as one PR. Many people work on dev, and keeping a stack of PRs rebased on it was causing conflicts. To make the review trackable, here is a coverage checklist in your suggested order. For each area it lists the main files and their tests. Platform paths are under futureagi/simulate/ unless they say otherwise, and ALK paths are in future-agi/agent-learning-kit#180.

  1. Version models, hashing, archives, migrations and backfill
    • Files: models/harness_versioning.py, migrations/0098–0102, services/canonical_json.py, services/harness_versioning/{hashing,archives,versions,diffs,read,backfill,backfill_check}.py, management/commands/harness_versions_backfill.py
    • Tests: tests/test_harness_versioning_{models,hashing,archives,record,history,backfill,backfill_check}.py
  2. Agent versions, credentials, preflight and change detection
    • Files: services/harness_agent_versions.py, services/harness_credentials.py, services/harness_agent_changes.py, services/harness_github.py, tasks/harness_agent_versions.py, tasks/harness_agent_changes.py, views/harness_github_webhook.py, views/harness_job.py (secret-values/), signals.py, the agent_definition model
    • Tests: tests/test_harness_agent_{versions,creation,provision,runs,source_api,changes,receiver}.py, tests/test_harness_github.py, tests/test_harness_github_webhook.py
  3. Drafts, Save/Discard and default moves
    • Files: services/harness_versioning/drafts.py, versions.py (move_default, set_defaults), services/hosted_harness_conversation.py (chat saves into drafts), views/harness_environment.py
    • Tests: tests/test_harness_versioning_{drafts,saves,rollback}.py, tests/test_hosted_harness_conversation.py
  4. ALK certification and proofs, platform run assembly and pinning
    • Platform files: services/harness_versioning/{assembly,run_warnings}.py, services/hosted_harness.py, hosted_harness_gateway.py, harness_scenarios.py, harness_provider.py, and the run responses run_results_v3*.py, views/run_test.py, serializers/response/run_test.py
    • Platform tests: tests/test_harness_versioning_{assembly,runs,picks}.py
    • ALK files: proofs.py, authoring_entrypoint.py (--certify-only), bundle_author_v2.py, world_import/sqlite.py, authoring_runtime_validation.py
    • ALK tests: tests/harness/test_certify_only.py, test_bundle_author_v2.py, test_world_import_sqlite.py, test_authoring_runtime_validation.py, tests/test_harness.py
    • Deploy dependency: the two PRs deploy together.
  5. Nudges and the rebuild lifecycle
    • Platform files: services/harness_versioning/{nudges,rebuild}.py, tasks/harness_nudges.py
    • Platform tests: tests/test_harness_nudges.py, tests/test_harness_versioning_rebuild.py
    • ALK files: nudge.py, hosted_chat_entrypoint.py, seed_rows.py
    • ALK tests: tests/harness/test_nudge_turn.py, test_builder_chat.py, test_seed_rows.py
  6. Versioning UI and run comparison
    • Frontend: frontend/src/sections/simulate/environments/workspace/{versions,agents,drafts,run,scenarios,overview,runs}/, VersionBar.jsx, EnvVersionPin.jsx, buildEnvironment/console/NudgeCard.jsx, frontend/src/pages/dashboard/harness/, and frontend/src/api/simulate-environments/, each with its __tests__
    • Backend: services/run_compare.py, tested in tests/test_harness_run_compare.py
    • The compare UI (workspace/runs/summary/RunCompare.jsx) is hidden.
    • API endpoints: tests/test_harness_versioning_api.py
  7. Supporting changes (the PR description's section O)
    • Files: tfc/logging/sentry.py, tfc/telemetry/middleware.py, tfc/settings/settings.py, the Temporal registry and schedules, simulate/urls.py, simulate/apps.py, model_hub/apps.py, the generated API contracts and MCP catalog, INSTALLATION.md, deploy/env-reference.toml
    • Tests: tfc/temporal/common/tests/test_registry.py, tfc/temporal/schedules/tests/test_register_schedules.py, tfc/tests/test_sentry_filtering.py, tfc/deployment_telemetry/tests/test_privacy.py, model_hub/tests/test_clickhouse_cache_warm.py

Live E2B:

  • Verified on the local stack: the platform API was driven as the signed-in user, against hosted E2B sandboxes from templates built from the ALK branch. Covered:
    • builder chat edits and Saves;
    • nudge rebuilds after GitHub pushes;
    • certify-only runs on cache misses;
    • simulation runs placing simulated voice calls against the test repo's LiveKit agent;
    • the steps in the PR's end-to-end list, most of them on earlier builds of the branch;
    • the three review-fix flows (runs 03d7d899, 86ef4ef6, f428502f) on the fix templates.
  • Not verified:
    • the browser UI, because the flows were driven through the API;
    • hosted-provider agents (Vapi, Retell).

Updated after the second review round: dev already writes AgentVersionSetup.setup_hash (dev 37abd769bb), so the TH-8534 row no longer says it hasn't started.

@hadarishav hadarishav left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focused re-review of the fixes. The normal stale-bundle carry path is improved, but the unpromoted-reseal case below still allows a saved environment to execute an older world. Please address it before resolving the original bundle-consistency finding.

Validation: 202 focused platform tests passed against an isolated PostgreSQL database with migrations skipped. A separate reproduction using a real ALK-generated bundle failed on the mismatch below. Hosted/E2B calls were not rerun.

The feature deferrals and current PR structure are accepted for this review. This is not approval of the entire PR.

except (S3Error, HostedHarnessError, ValueError):
return False
held = default.get(SEALED_MANIFEST)
return held is None or archives.sha256_hex(held) != manifest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] An unknown manifest does not prove that the bundle was resealed for the current content

This returns true for any manifest not present in the current drafts/default. record_draft() then stamps the current environment/suite hashes onto it, and _save_pair() trusts that association. A bundle from an earlier, unpromoted reseal can therefore be saved for a later world.

The failure path is reachable across the chat lifecycle: ALK can reseal and then fail to publish, leaving the bundle in a warm sandbox. A later turn edits the world but fails to reseal. _publish_if_changed() reports a warning, but _handle_command() still checkpoints and emits outcome: success, which the platform schedules for promotion. The earlier manifest is unfamiliar here, so it is classified as newly resealed for the later edit.

I reproduced the platform save with a real ALK-generated, validated bundle: the saved authoring SQLite contained second-edit, its sealed seed contained first-edit, and default_pair_sealed was true. A separate ALK reproduction confirmed a failed reseal still emits a promotable success event. Ordinary explicit discard retires the sandbox; this finding concerns unpromoted reseals.

Please bind the bundle to the authoring content it actually sealed, or conservatively require certification when that association cannot be verified. Also prevent a failed reseal from being promoted through the successful-turn fallback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 038acb5b8 (platform) and future-agi/agent-learning-kit d3d153dc.

  • ALK now stamps a resealed bundle's manifest with metadata.sealed_from: a digest of the authoring files the reseal carries (contract.json, sub_goals.json, world.sqlite, scenarios/). A reseal that cannot carry the world leaves no stamp.
  • _resealed trusts a chat's bundle only when that stamp matches the tree being saved and the bundle's other environment files are unchanged from the default's. A bundle from an earlier, unpromoted reseal carries the earlier world's stamp, so it is not trusted: the pair is saved unsealed and its first run certifies it.
  • A chat turn whose reseal fails now ends with outcome: failed through the same path as other failed turns, so its checkpoint is not promoted.

Tests:

  • Platform: test_harness_versioning_reseal.py, including test_a_bundle_stamped_for_an_earlier_world_is_not_resealed, test_an_unstamped_bundle_is_not_resealed and test_a_bundle_resealed_beside_an_edit_it_does_not_carry_is_not_resealed. The drafts, record, rebuild and runs fixtures now model the stamp.
  • ALK: test_a_failed_reseal_ends_the_turn_failed_with_nothing_to_land, test_a_bundle_is_stamped_only_when_a_reseal_carries_its_world, test_a_bundle_keeps_the_stamp_of_the_world_it_was_sealed_from.

Still open, marked # TODO: in _resealed: a contract.json-only edit still reuses the resealed bundle. The reseal copies contract.json, but not what a build derives from it. Treating any contract change as unsealed broke rebuilds that add a tool, so this needs a check against ALK first.

Local E2E: a chat world edit was saved unsealed (environment v15, suite v6; the chat's bundle was not reused), then the run certified the pair with --certify-only and completed 1/1 calls (run 59572bb5). The path where a stamped chat bundle is accepted was not reached live; the reseal tests cover it.

@hadarishav hadarishav left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completed the broader review against the seven-section checklist at platform 234fb00788c9c4c402c97558701267015dde20d0 and ALK 2e3a4b87aa747e5d64246947f26b2b1559b9fca8. Four additional findings are attached inline. These are separate from the already-posted stale-bundle and SQLite-default findings. Approval remains withheld pending the correctness fixes.

Checklist section Coverage and result
1. Models, hashing, archives, migrations/backfill Inspected identity, constraints, archive validation, backfill safeguards and tenant filtering. No additional confirmed finding.
2. Agent versions, credentials, preflight and detection Traced creation, preflight, activation, secret resolution and GitHub observation. Confirmed pending credential rotation can be undone by a push.
3. Drafts, Save/Discard and defaults Traced save/discard, restoration and concurrent chat writes. Confirmed rebuild landing can activate a newer unchecked draft.
4. ALK certification/proofs and platform assembly/pinning Inspected proofs, bundle assembly/cache, selected versions, scenario overlays and launch. Previously posted correctness findings remain.
5. Nudges and rebuild lifecycle Traced dispatch, gates, checkpoint promotion, completion, retries and stop/recovery. Confirmed landing race and partial credential disclosure in report redaction.
6. UI and APIs Static review of version/draft/run controls and caller integration. Checked changed frontend modules for unresolved local imports and UI route templates against OpenAPI. No additional confirmed finding; no browser/Vitest run.
7. Supporting changes Inspected telemetry, schedules, flags, startup guards and rollout instructions. Confirmed documented Compose backfill write command is rejected.

Verification: 1,695 backend test passes in the selected suites. Four phone-fixture failures passed after supplying the missing test-only caller-ID configuration. The broader ALK selection finished with 544 passes, three skips and three failures also reproduced on the base branch. Independent reproductions confirmed the findings; external preflight probes were mocked, and the rebuild race used a controlled database-backed interleaving through the real service paths.

This was a thorough section-based review, not exhaustive verification of every changed line or runtime flow. Frontend review was static; no browser/Vitest or live E2B/Vapi/Retell execution. Migrations were inspected but not executed against representative existing data; backend test schemas used --nomigrations. Generated contracts were checked structurally, not manually line by line. These limits mean passing tests are not evidence that these are the only remaining defects.

The accepted feature deferrals and current PR structure are not being reopened. After the fixes, the next verification priorities are migration/backfill against existing data and browser-to-hosted flows.

if report is None:
close(state=State.FAILED, error={"code": "no_report"})
return nudge
draft = _result_draft(locked, nudge)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Bind rebuild landing to the draft that passed checks

Checkpoint promotion and landing happen separately. land_nudge_result reads the passing report from the nudge checkpoint, then selects the latest fresh draft without matching its hash/revision to that checkpoint. A run-chat edit in between can overwrite the draft and be activated by the old report. A database-backed deterministic interleaving through the real run-chat compose/enqueue/promote path changed the draft from nudge revision 1 to chat revision 2; landing reported applied and made the unsaved chat change live. This reproduces with certification enabled and an unsealed save, independently of the previously posted stale-bundle issue. Bind the report to the checked draft hash/revision and refuse landing if it changed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9e23648e2. Before landing, land_nudge_result now checks whether another chat saved over the rebuild's result (_saved_over), and if so fails the landing with draft_changed:

  • a fresh draft the rebuild didn't write, whose hash differs from the draft it set aside at the start; or
  • with no draft left, a draft this turn's chat discarded by saving the default (scoped to the turn's start, set-aside drafts excluded).

restore_draft_snapshot no longer brings the turn-start draft back over that save.

Tests in test_harness_versioning_rebuild.py: test_a_run_chat_save_before_the_landing_is_not_landed[edit|unchanged], test_a_run_chat_save_of_the_default_before_the_landing_is_not_landed[default|draft], test_a_run_chat_save_before_a_landing_retry_is_not_landed, and test_unsaved_edits_the_rebuild_leaves_as_they_are_still_land. Both cases of test_a_run_chat_save_of_the_default_before_the_landing_is_not_landed fail on the old code.

Not reproduced in the local E2E: the window between checkpoint promotion and landing can't be hit through the API, so this is covered by tests only.

environment.id,
)
return "skipped_not_runnable", None
parent = environment.agent_version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve pending credential rotations when observing a GitHub commit

_observe_one always creates the push version from environment.agent_version, ignoring a pending credential request. Reproduced A1 old key -> pending A2 rotated key -> GitHub observer creates A3 from A1 -> A2 passes/activates -> A3 passes/activates. The final pinned credential is the old key. UI restrictions cannot prevent background polling/webhooks from doing this. Carry forward the outstanding requested version or defer observation while it is pending. External preflight calls were mocked; database version creation, encryption/decryption, activation and pinned secret resolution were real.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bee5835ae. _observe_one now builds the push version on the outstanding requested version when there is one (_outstanding_request), so in your sequence A3 is built from A2 and keeps the rotated key. _outstanding_request also counts a change that passed while the environment was busy (environment_busy), which was the other way the old key could return.

Tests in test_harness_agent_changes.py: test_a_push_while_a_credential_change_is_checked_keeps_its_values[first|second] (either version activating first), test_a_push_on_a_failed_credential_change_is_asked_again_once_fixed, and test_a_push_after_a_credential_change_passed_while_busy_keeps_its_values.

Not run live: the push to the test repo was blocked on the local stack, so rotation plus push is covered by tests only.

),
]
masked = _redacted(
environment, nudge, [text[:cap] for _part, _at, text, cap in secret_parts]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Redact rebuild diagnostics before truncating them

_report truncates strings before _redacted runs. A credential crossing the cutoff becomes a prefix that no longer matches the known full secret and survives into persisted summary/error fields exposed by nudge_item. Running the actual report and redaction functions with a fake credential showed normal redaction replacing it with [REDACTED], while report output retained FAKE-CREDENTIAL-123. Redact complete strings before limiting their displayed length. This confirms partial secret disclosure, not an entire usable credential.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0f7848ad2 (platform) and future-agi/agent-learning-kit c393f7b3. _report now masks before it cuts. Each text is:

  1. normalised (control characters and ANSI escapes stripped);
  2. cut to its cap plus 16 KiB (SECRET_VALUE_MAX, longer than any credential value), so a value across the cap is still whole and masking stays cheap;
  3. masked;
  4. cut to its cap.

RESULT_FILE_MAX is now 4 MiB. On the ALK side, texts are kept up to 4000 characters and each list to 50 entries, so a value is not cut before the platform masks it.

Tests:

  • Platform: test_a_value_across_a_cap_is_masked_before_the_report_cuts_it, test_only_a_bounded_prefix_of_each_text_is_masked, test_control_characters_cannot_push_a_value_past_the_masked_prefix.
  • ALK: test_texts_the_platform_masks_keep_a_value_whole_past_its_500_cap, test_the_result_keeps_only_what_the_platform_reads.

Covered by tests only; not exercised in the local E2E.

Comment thread INSTALLATION.md Outdated

```bash
docker compose exec backend python manage.py harness_versions_backfill --check
docker compose exec backend python manage.py harness_versions_backfill

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Authorize the documented backfill write command in Compose

Both supported Compose configurations set NO_STARTUP_DB_MUTATIONS=true. The documented --check command is allowed, but the following write command is rejected by model_hub/apps.py during Django startup. Verified the actual guard functions reject that invocation and accept explicit local mutation mode. For self-hosted Compose, include docker compose exec -e NO_STARTUP_DB_MUTATIONS=false backend python manage.py harness_versions_backfill (app for Standalone); hosted rollout should use its authorized operator workflow.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fa3f923c4. INSTALLATION.md now runs the write as a one-off operator process. The three variables apply to that process only; the running services keep NO_STARTUP_DB_MUTATIONS=true.

docker compose exec backend python manage.py harness_versions_backfill --check
docker compose exec -e SERVICE_TYPE=bootstrap -e STARTUP_DB_MUTATION_MODE=operator -e NO_STARTUP_DB_MUTATIONS=false backend python manage.py harness_versions_backfill

Use app in Standalone. Helm has the same form through kubectl exec ... -- env SERVICE_TYPE=bootstrap STARTUP_DB_MUTATION_MODE=operator NO_STARTUP_DB_MUTATIONS=false python manage.py harness_versions_backfill. deploy/env-reference.toml points at that section.

Test: model_hub/tests/test_clickhouse_cache_warm.py::test_the_documented_harness_backfill_run_is_allowed[local|production].

Local E2E:

  • the plain write command was refused by the startup guard, as you found;
  • --check reported 6 environments current, 0 errors;
  • the documented operator form ran (current 6).

@KarthikAvinashFI KarthikAvinashFI left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend architecture review (frontend and tests not reviewed here). The model follows the PRD's decoupling and reuses the existing pieces well (AgentVersion side table, build_run_comparison extended, canonical_json moved, HostedSourceAcquirer reused). Requesting changes for one architectural issue plus a few duplicates, and the branch needs latest dev (402 behind, 22 conflicting files, migration 0098 collides with dev's 0098_agent_version_setup).

  1. Identity is re-implemented from ALK's layout: tree_digest is a "byte-for-byte port" of ALK's _artifact_digest (hashing.py:133), and which ALK files count is hard-coded in hashing.py:50-69 and archives.py:37-60. Every new file ALK writes changes identity until it is listed here; the world-hash bug was this drift. ALK already writes world_ir_hash and scenario_set_hash into its certificate; can the platform store identities ALK reports instead of re-deriving them? Worth checking generic-harness/setup-checks-passed.json, which is on none of the lists.
  2. run_compare.wilson_interval (run_compare.py:44) duplicates run_reliability_v3._wilson (run_reliability_v3.py:16); one helper for both.
  3. compare in views/harness_environment.py:508-581 resolves runs and applies status rules in the view; please move that to a service.
  4. Migrations 0100 to 0102 alter tables created in 0098 in this same PR; fold them into one when renumbering on top of dev.
  5. dev now has AgentVersionSetup (simulate/models/agent_setup.py:9), and the plan detects provider changes by its setup_hash, but none of this PR's backend uses it; after merging dev, provider-change detection should read it instead of keeping a second record.
  6. The removal of the author_hosted_harness_job activity is unrelated to versioning; it belongs in its own small PR.

Note on this review. Given the size of the diff, this review was done with AI assistance and I have not yet gone through it line by line myself, so please treat it as a first pass. It is a code and architecture review of the backend at 234fb007 against the PRD, the plan and our backend standards. The frontend and the test files were not reviewed, and nothing was run: no tests, no frontend, no builds and no live runs. Each finding cites the line it was read from, but context outside the diff may be missed, and I will confirm or drop points as we discuss them.

def tree_digest(
files: Path | Files, *, exclude: Callable[[str], bool] | None = None
) -> str:
"""Byte-for-byte port of ALK ``authoring_runtime_validation._artifact_digest``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A byte-for-byte port of ALK's digest means the two must change together forever. ALK already writes world_ir_hash and scenario_set_hash into its certificate; can the platform store identities ALK reports instead of re-deriving them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in part through 32df8c74c (see the reply on hashing.py:51). The unhashed list in hashing.py now names only root-level files. archives.py still lists which generic-harness/ records travel with the bundle, and that list gained two entries this round.

On storing what ALK reports:

  • ALK writes world_ir_hash, scenario_set_hash and contract_hash only when runtime validation certifies. Chat edits and Scenarios-tab saves are versioned before any certify, so for them the certificate is stale or absent.
  • They also measure different things:
    • world_ir_hash embeds the source-model fingerprint, so the agent's source digest. Every agent commit would mint a new environment version.
    • scenario_set_hash covers checks/ and __pycache__.
    • contract_hash covers raw contract bytes, including chosen_evals and the provider prompt, which the platform leaves out on purpose.

tree_digest keeps ALK's byte layout only so both sides compute the same digest for the same tree. It does not decide which files count; the lists do.

A follow-up could have ALK write a manifest of identity files, with the platform hashing exactly those. That would remove the lists.

# In an authoring tree but not part of the environment's identity: the contract is
# hashed separately; the rest embed the agent's source digest, belong to the suite or
# to a build, or are run noise.
ENVIRONMENT_UNHASHED_FILES = frozenset(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lists (and the ones in archives.py:37-60) hard-code ALK's workspace layout, so every new file ALK writes changes identity until it is listed here; the world-hash bug was this drift. Worth checking generic-harness/setup-checks-passed.json, which is on none of them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and fixed in 32df8c74c. generic-harness/setup-checks-passed.json is runtime validation's resume cache: a list of keys over the bundle, the invariants and each scenario's folder. Any validation pass, even for a suite-only change, rewrote it, and it landed on the environment side and was hashed, so it minted a new environment version.

What changed:

  • hashing.py: the whole generic-harness/ root is now outside environment identity. ALK keeps only derived records and caches there, and its own candidate hash skips generic-harness/ too. A file ALK adds there later can no longer move the hash.
  • archives.py:
    • setup-checks-passed.json and runtime-plan-repair-* are sealed, so they travel with the bundle they describe.
    • provider-import-profile.json (the provider agent's fetched definition) is no longer stored in a version, so a provider prompt edit no longer changes the environment hash.
    • source-data-review-early-error.txt is dropped as a log.
  • Tests: test_harness_versioning_hashing.py::test_ignores_build_outputs_suite_files_and_noise (the cache file and a future generic-harness/ file) and test_harness_versioning_archives.py (sealed and dropped sets).

Side effect: trees built on this branch hold the cache file, so their recomputed hash changes once. The first chat save after this change mints one new environment version. Local E2E showed this: a save with no world edit moved environment v13 to v14, and its change summary counted two world files changed. Versioning is off on dev and the backfill runs after deploy with the new rule, so this only affects environments versioned from this branch.

_UNRUN_OUTCOMES = ("queued", "in_progress", "error")


def wilson_interval(passed: int, trials: int) -> tuple[float, float]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates run_reliability_v3._wilson (run_reliability_v3.py:16); one helper for both.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 44ee13ade. run_compare.wilson_interval now calls run_reliability_v3.wilson (renamed from _wilson). It takes an optional z so compare keeps its exact z; with 1.96, one of Newcombe's published values differs in the fourth decimal. No test change; the existing compare and reliability tests pass unchanged.

reject_unknown_fields=True,
)
@action(detail=True, methods=["get"], url_path="compare")
def compare(self, request, pk=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resolves runs and applies status rules in the view; please move that into a service and keep the view to validate, call, respond.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, no code change in this PR. The compare endpoint and UI are hidden here (RUN_COMPARE_ENABLED = false), and compare as a whole moves to TH-8657, which waits on the designs and flows. Its response contract changes there, so the run resolution and status rules will move into run_compare then, rather than being moved twice.

from django.db import migrations, models


class Migration(migrations.Migration):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0100 to 0102 only alter tables created in 0098 in this same PR; fold them into one migration when renumbering on top of dev (where 0098 is now taken).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. 0098 to 0102 have to be renumbered on top of dev's 0098_agent_version_setup anyway, and they have not shipped anywhere, so folding them into one migration after dev's latest is safe. Merging dev and the fold are pending the author's decision and will be handled in a follow-up push.

@JayaSurya-27

Copy link
Copy Markdown
Member Author

@KarthikAvinashFI, replies to the review-body points that have no inline thread. The inline replies cover the rest. Fixes from this round are pushed to feat/environment-versioning, now at 44ee13ade.

Merge latest dev: pending the author's decision; it will be handled in a follow-up push. A dry-run merge shows 22 conflicting files, and migration 0098 collides with dev's 0098_agent_version_setup. The migration fold (0098 to 0102 into one after dev's latest) goes with it.

#5, AgentVersionSetup.setup_hash: agreed, once dev is merged. Dev's store_agent already writes AgentVersionSetup.setup_hash (dev 37abd769bb, TH-8534). This PR has no provider-change detection for hosted Vapi and Retell agents and keeps no second record of provider setup. Hosted-provider agents get no new agent version when their provider setup changes (scope table, TH-7948). When that detection is built, it will compare setup_hash rather than keep its own record.

#6, the author_hosted_harness_job removal: it has no caller on dev or here; its only references are its definition and the registry. Splitting it into its own PR is cheap (three files). Whether to split it is pending the author's decision and will be handled in the follow-up push.

@JayaSurya-27

Copy link
Copy Markdown
Member Author

@hadarishav, thanks for the second round. All six findings are answered inline. Platform is now at 44ee13ade and future-agi/agent-learning-kit#180 at 694a1e3e:

Finding Fix
Unpromoted reseal trusted as new 038acb5b8, ALK d3d153dc; a contract.json-only edit is still open (TODO)
Rebuild landing over a newer draft 9e23648e2
Pending credential rotation undone by a push bee5835ae
Report cut before masking 0f7848ad2, ALK c393f7b3
Backfill write command in Compose fa3f923c4
SQLite defaults on repair writeback ALK 8df36a4f, 09e7bc68

Full platform and ALK suites were run on both heads. Every failure in them also fails on the pre-fix base.

Local E2E on the full stack, driven through the platform API against hosted E2B sandboxes:

  • Covered:
    • the backfill: --check, the plain write refused by the startup guard, and the documented operator form;
    • a chat world edit saved unsealed, then certified and run (run 59572bb5, 1/1 calls);
    • a control run of the defaults (run eb3aba24, cached bundle replayed, completed).
  • Not run live:
    • credential rotation plus a push, and the new-tool warning, because the push to the test repo was blocked locally; both are covered by tests only;
    • the rebuild landing race, which can't be reached through the API; tests only;
    • the browser UI, and migrations against representative existing data.

The certify runs on a sandbox template built from the latest ALK head timed out on one scenario's ready check, while the same pairs certified and ran on the previous template. That points at the template build. The release template will be rebuilt and verified before versioning is enabled.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants