Repository navigation
feat(bundle): write MCP server, website and multi envs from their env.toml - #73
Merged
Merged
Conversation
…e when an executable bit changes Three changes to the bundle ledger, which envs written from env.toml will build on. A version an interrupted run wrote is kept. The ledger now stamps a write's version on its pending row as soon as the write returns, before it re-checks what the write held, which can take seconds. The next run of the bundle keeps that version, rather than writing it again, when it's still the store's latest and its inputs haven't changed. The run and the dry run report it as "unchanged (written by an interrupted run that didn't record it)". A version someone else wrote since is written over, as before. A built image's executable bits are inputs. A build copies each file's mode into the image, so a chmod alone now rebuilds it. A file with no executable bit is hashed as before, so the images a bundle built without one are still reused. One helper, unpinned_store_refs, names the store refs a write gives no version. Its writer pins them and the ledger hashes them from the same mapping, for every kind whose writer pins, not only envs and agents. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… file is executable A build copies each file's permission bits into the image, so 0755 to 0700 changes what runs in it, but both were hashed as executable. A file's mode now joins its digest whenever it isn't the usual 0644, so images built from such files are reused as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
….toml
An env folder becomes an env: an mcp_server (a Dockerfile alone is enough), a website or a
multi, written over the images built from the folder or named in the store.
- Images: an image key left out builds the folder's Dockerfile, or Dockerfile.backend and
Dockerfile.frontend for a website, as <id>__env_image or __backend_image / __frontend_image;
{ dockerfile = "..." } names another; an image table takes only dockerfile.
- environment_name: env.toml's, else the one @environment_card(name=...) in the source the image
is built from, else refused. A store image needs it set.
- env.toml takes a closed set of keys per type and no [metadata]; env_provider_type is checked as
the put commands check it, and a name a gateway deploy gives its own containers is refused.
- A multi's mcp_server_envs and website_envs are typed refs, checked before any write for bundle
and store envs alike, and two of its MCP servers or websites can't share a name.
- gateway_server and service_db folders are refused: config names those, and a run builds them.
- An env folder whose id is another type of env in the store is refused before any write.
- The env types the core's from_toml writes are tracked by the ledger, so an unchanged env is
reused and one whose image or child is written anew is rewritten. A plugin env type with a
from_toml of its own is refused, as before.
- The run's preflight checks a bundle env from its planned config: the infra its gateway deploy
needs, and an image the bundle builds as one only this machine has.
A conformance test checks that each type's toml_refs name keys it takes, that its from_toml loads
exactly what they declare, and that what it writes references nothing else.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Base automatically changed from
edgararakelyan/ledger-pins-modes-orphans
to
main
October 6, 2026 19:07
# Conflicts: # src/agent_env/bundle/ledger.py # src/agent_env/bundle/materialize.py # src/agent_env/bundle/plan.py
earakely-scale
marked this pull request as ready for review
October 6, 2026 19:09
…te, and preflight the planned versions A multi's MCP servers and websites run as containers their environment_names name: an MCP server as one by that name, a website as its backend's and frontend's. Two of a multi's envs could name one container across the two lists, which the check missed. It now compares the containers. On a provider other than the core's, an MCP server and a website of one name are refused here too, not only when the multi is written. The preflight read a store image or child env named without a version at its latest, while the writer pins the version the plan read; it now reads that version, and so does the check of a bundle agent's store image. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…check it by calling it An MCP server built from its folder and named by its card, one built from docker/Dockerfile under a name of its own, one over a store image a CLI put wrote, a website, and a multi of bundle and store envs: each is deployed on the local sandbox through the gateway and answers a verifier that calls its tools or pages. An edit to a multi's child rebuilds that child alone and rewrites the multi, which then serves the new build. A multi whose children name one container is refused before anything is built or written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e deploy path's provider rules _toml.py goes. The checks every env.toml gets now live on AuthoringContext as accept_env and accepted_env, beside accepted and card_names, so a plugin env type writing its own from_toml can make them too. The MCP server and website accept_toml are each one call. What a provider can't deploy is one function, provider_refusal, in _deployment.py: the server provider deploys one MCP server, and a plugin's provider can't tell a multi's MCP server and website of one name apart. deploy_refusal, the env.toml check and the plan's multi check all call it, so the bundle refuses exactly what a deploy would. The one_server flag and the function-level provider imports go with it. Also: the environment_name lookup returns its name and problem rather than writing into the fields; ctx.config_problem prefixes a problem with the entry's toml for env and agent tomls alike; and the accepted fields carry env_provider_type, defaulting to the gateway provider's type, so from_toml no longer repeats the default. Wording: an unknown provider type reads 'env.toml: Unknown env_provider_type: ...', and the multi refusal reads 'gives a multi one env card, so it can't tell ...' at deploy and in a bundle alike. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A bundle's
envs/<name>/folders become envs: an MCP server (a Dockerfile alone is enough), a website or a multi. Each is written over the images built from its folder or named in the store, andagent-env rundeploys it.It builds on #71's pinning helper.
What an env folder holds
Images. A key left out builds the folder's Dockerfile, or
Dockerfile.backend/Dockerfile.frontendfor a website.{ dockerfile = "docker/Dockerfile" }names another one, still with the folder as context. A bare id or{ artifact = "…", version = n }names a store image.dockerfile; anything else, such asbuild_args, is refused by name.environment_name. It's
env.toml's if set. Otherwise it's the single@environment_card(name=…)in the.pyfiles the image is built from (the Dockerfile's folder first, then the whole folder), asenv mcp-server putreads it.ENVIRONMENT_NAME, which the SDK prefers.Keys. Each type takes a closed set, with no
[metadata]and nothing stamped:mcp_server:image,environment_name,env_provider_type.website:backend_image,frontend_image,environment_name,env_provider_type.multi:mcp_server_envs,website_envs,name,env_provider_type.A multi's
nameleft out is random per deploy, as withenv multi put.Checked before anything is written
env_provider_typeno installed provider has, or one that deploys a single MCP server, on a website or multi.environment_namethat's one of the names a gateway deploy gives its own containers (gateway,servicedb,pgweb,db-mcp,website-browser).namewith whitespace, or a child of the wrong type. Children are typed refs (EntityRef.env_type), checked in the resolver for bundle envs and in the plan for store envs.<name>-website-backendand<name>-website-frontend.gateway_serverorservice_dbfolder. Config names the one every deploy uses, and a run builds it when it's missing.The env.toml checks are
AuthoringContext.accept_env/accepted_env, besideaccepted, so a plugin env type's ownfrom_tomlcan make them too. The provider rules areprovider_refusal, the same functiondeploy_refusaluses, so a bundle refuses exactly what a deploy would.bundle checkandplugin checkreport the store-free ones without reading a store.Writing and reuse
from_tomlreads only the toml and what it names are written and tracked by the ledger: the core's (MCPServerEnv,WebsiteEnv,MultiEnv) and any type that inherits one of those, orEnv's.from_tomlof its own is still refused, since the ledger can't list what it reads.unpinned_store_refs).Deploying
The run's preflight walks an env the bundle writes from its planned config, as it walks a store env.
Limits
env.toml, rebuilds both.<role>_imageleft out buildsDockerfile.<role>" applies to any image key a type declares; today only the website's two use it.Tests
env_toml_test), with docker stubbed:environment_nameover the card, and a Dockerfile in a subfolder;toml_refs_test): each type'stoml_refsstart at keys it takes, itsfrom_tomlloads exactly what they declare, and what it writes references nothing else. This coversMCPServerEnv,WebsiteEnv,MultiEnv,A2AAgentandEval.agent-env runwith real docker builds on the local sandbox:docker/Dockerfileunder its ownenvironment_name(its<name>_add_itemtool proves the name reached the server); one over a store image a CLI put wrote; a website, with its frontend page and its backend's health checked through the gateway; and a multi of a bundle MCP server, a store MCP server and the bundle website.End to end
Real runs on macOS arm64 with Docker, from a fresh state root, on the local stores and the local sandbox provider. The bundle holds:
items, the integration suites' items server, as an MCP server folder;shop, the Slack website, its two Dockerfiles at the folder root and the files they copy inside the folder;suite, a multi of items, a store envmailput by the CLI, and shop.Each has a task deploying it.
items(its card) overitems__env_imagev1; shop is a website over__backend_image/__frontend_image; suite is a multi of items v1, mail v1 and shop v1--sandbox modalmailbeside the store's mail in suitegateway_serverfolderNo refusal wrote anything, and no run left a sandbox folder or container.
🤖 Generated with Claude Code
The PR appears safe to merge; no new actionable issue or outstanding previous finding remains.
Summary
Bundles can now build or reuse images from
envs/<name>/and write MCP server, website, and multi envs foragent-env runto deploy. Planning checks env settings, references, names, and deployment needs before writes begin.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A["Bundle folders"] --> B["Resolve and check"] B --> C["Build images"] C --> D["Write envs"] D --> E["Check deployment"] E --> F["Run tasks"]Reviews (5) · Last reviewed commit: "refactor(bundle): check env.toml in the ..."