Blueprints
Section titled “Blueprints”Your agent’s program has to run somewhere. That somewhere is an environment the server builds for each run: the Packages the program can import, the files it can see, the secrets its Packages may use, and the rules for all of it. A Blueprint is the plan for that environment. It is to the environment what a blueprint is to a house, or an image to a container. It is the definition the environment is built from. The server keeps the plan under a name. Each time your application opens a session and names it, the server builds an environment to that plan and runs the agent’s programs inside. What the plan doesn’t include, the environment doesn’t have.
The plan is written once, by you (or your coding assistant, with our skill), outside the model, and the runtime reads it on every call. In the quickstart you wrote one with a single rule and watched the environment refuse one call. This chapter is the plan in full: what it says, how a call is decided against it, and where its power stops.
What the plan says
Section titled “What the plan says”Before the details, the shopping list. A Blueprint has these parts:
- Packages. The Submilli Packages the program may import. They are like npm packages, but written for Submilli, so every operation in them can be checked against the rules.
- Permissions. What the program may do, and what each Package may do on its behalf. These are the rules, per caller, for every operation that reaches outside, and the default when no rule matches.
- Secrets and variables. Secrets are credentials, declared by name, with their values kept outside the file. Variables are values your application binds when it opens a session, for the rules to test.
- Auth proxy. When the program uses HTTP directly, this adds the credential on the way out, without exposing it to the model.
- MCP servers. Tool servers you already run, declared here so they become Packages the program can import.
- Files. What filesystem the program sees: nothing, a scratch directory for the run, a directory that lasts the session, or a volume the server’s operator declared. How long an idle session lives is set here too.
The rest of this chapter is about the rules, because that is where the plan does its work. The other parts each get a page in the how-to part on Blueprints.
Nothing is allowed until you say so
Section titled “Nothing is allowed until you say so”The smallest Blueprint permits nothing:
kind: blueprintname: supportdefault: denypermissions: main: []name is what the application and the server call it. permissions is a
map from caller to a list of rules. main is the generated program.
default is the answer when no rule matches. Leaving default out means
deny as well, so a Blueprint containing only a name denies everything.
Under this file, a program that computes and returns a value runs fine. Anything that reaches outside the instance, such as a file, a request, or a Package operation, fails with a permission error. Each rule you add widens that.
A rule has three fields: capability, the name the Package or the standard
library gave the operation; an optional filter over the fields the
operation reports; and action, allow or deny.
permissions: main: - capability: acme.com/credits.apply filter: customerId == ${vars.customerId} and customerClass == "premium" action: allowWhen a program calls a gated operation, the runtime finds the caller’s list,
walks it top to bottom, and takes the first rule whose capability matches by
name and whose filter is absent or true. If none matches, default decides.
Names match exactly. There is no fs.*, so allowing fs.write doesn’t
allow fs.mkdir.
Filters compare a field with ==, !=, <, <=, >, >=, test a
pattern with glob or matches, and combine with and, or, not. A
field the operation didn’t report never matches.
Code you trust and code you don’t
Section titled “Code you trust and code you don’t”Two kinds of code run inside one program: the Packages you reviewed and
installed, and the code the model wrote a moment ago. Submilli keeps them
apart, and the Blueprint gives each a separate list of rules. main holds
the rules for the generated code, and each Package has a list under its name. Permissions are per
caller, not per program.
A Package declares its capabilities. It provides operations a
program can be granted, such as acme.com/credits.apply. It requires
what its own code needs to do its job, such as an HTTP request to the
billing host and the secret that authenticates it.
permissions: # What generated code may do. main: - capability: acme.com/credits.apply filter: customerId == ${vars.customerId} and customerClass == "premium" action: allow
# What the package itself may do. '@acme/billing': - capability: http.post filter: host == "billing.internal.example.com" action: allow - capability: secrets.get filter: name == "BILLING_API_KEY" action: allowUnder this Blueprint, generated code can’t send a request to the billing
host or read the key. It can call applyCredit, and the Package sends the
request, authenticated with the key. Generated
code can use the billing API only through the Package’s function. The CLI writes a Package’s own list from what it requires when
you add it. What it provides, you grant to main, narrowed with filters
and variables.
Semantic permission model
Section titled “Semantic permission model”A semantic permission model defines which business operations an agent may perform, on which resources, and with which argument values.
Consider the alternative. You run the agent in a secure sandbox, or route
all of its traffic through a gateway, and write rules over what it sends. To
write a rule such as “may credit the customer it is serving, and only a
premium one”, you would have to reverse-engineer the traffic. You would find
the request that means a credit among everything posted to
billing.internal.example.com and work out which field is the customer and
which the amount. Then you would do it again for each service the agent
uses, and again when a service changes its API.
And the fact the rule most needs, the customer’s class, isn’t in the
request at all.
Submilli inverts that. The Package that performs the operation says what it
means. acme.com/credits.apply means applying a credit, and the Package
hands the runtime the customer, the amount, and the customer’s class, typed,
before anything is sent. The rule is written against those fields, never
against a raw payload. Nobody guesses what a request does, and the model is never asked
to judge its own intent. These are semantic permissions, and the
next chapter shows where the meaning comes from.
Context: who the session is for
Section titled “Context: who the session is for”The other thing a sandbox or a gateway can’t see is context, meaning which customer this conversation is about. The request doesn’t carry it, and the model can’t be trusted to state it. A variable brings that context into the rules. Your application binds it when it opens a session, from what it knows, and a rule can test against it. You write one Blueprint and bind a different customer for each session:
variables: customerId: required: true
default: deny
permissions: main: - capability: acme.com/credits.apply filter: customerId == ${vars.customerId} action: allowThe filter compares two values. customerId is the customer the program is
asking to credit, supplied by the Package in the permission check.
${vars.customerId} is the customer the application authorized for this
session, taken from trusted context such as the signed-in account and
supplied outside the generated program. With cus_northwind bound, a
credit for cus_initech fails the rule even though applying credits is
allowed, and nothing the program does can change the binding.
The application or harness opens a session for one conversation with the agent, and then runs each program inside it. The variables are bound when it opens and last as long as it does.
Secrets stay on the trusted side
Section titled “Secrets stay on the trusted side”Anything generated code can read, the model can be talked into repeating. That holds for whatever an allowed operation returns. The Blueprint decides what a program may fetch, and has no say over what the model says afterwards. So credentials must be unreadable altogether.
The Blueprint declares each secret by name and says where the runtime finds the value: a secret store, or the application when it opens the session. The value never enters the file.
- A Package reads a secret by name with
secrets.get. The runtime refuses the same call frommainwhatever the Blueprint says, even one that allows it. - When a Blueprint lets generated code call an HTTP endpoint directly, Submilli’s auth proxy adds the credential outside the program. The program sees the response, never the header.
Next: Packages, where the operations a Blueprint rules on come from.