This guide shows you how to build a Blueprint block by block with the
submilli blueprint commands, test it, and register it on a server. The
examples use the billing Package from Packages.
Substitute your own Package, operation, and fields.
The commands edit blueprint.yaml in the current directory and rewrite it
each time, so comments you add by hand don’t survive them. Refer to the
Blueprint file reference for every
block and field.
Install the Package
Section titled “Install the Package”A Blueprint can only list a Package that is in your local store. If the
Package is your own, submilli build publish-local from its project puts
it there. If someone else published it, install it from its repository:
submilli install acme/billing-package @acme/billingThe first argument is the GitHub repository, owner/repo. The second is
the Package to build from it, since one repository can hold several. Leave
the Package out to install all the Packages the repository declares. install
fetches the repository, builds the Package, and puts it in the local store,
pinned to the commit it resolved. To pin a branch, tag, or commit yourself,
append @<ref> to the repository name. This installs the curated Jina
Package at the runtime’s v0.2.0 tag:
submilli install submilli/submilli-runtime@v0.2.0 @submilli/jinaAdd --upgrade to replace a Package already installed at another commit.
The curated Packages all come from
that repository.
From a private repository
Section titled “From a private repository”A private repository needs a GitHub token that can read it. Without
one, install reports that it found no public repository by that name.
Store yours once, and install and build send it from then on:
submilli github authenticate✓ stored a GitHub token for octocat (never expires) in ~/.submilli/github_tokenauthenticate prompts for the token, or reads it from piped standard
input, and checks it with GitHub. In CI, put the token in GH_TOKEN.
submilli github auth-status says which token applies. Refer
to Install private Packages on a
server for
the token’s settings, and to the CLI
reference for the errors an install can
give.
On a server
Section titled “On a server”A server has its own Package store, so a Blueprint that will run there
needs the Package installed there too. The submilli server commands talk
to a running server, and Connect the CLI
shows how they reach it:
submilli server packages install acme/billing-package @acme/billinginstalled @acme/billing @ 3f9c2a1b7e40The server fetches, builds, and pins the Package the same way. --sha <ref>
pins a commit, tag, or branch, and --upgrade replaces an installed one.
Read the Package’s capabilities and docs
Section titled “Read the Package’s capabilities and docs”Before granting anything, read what the Package provides:
submilli blueprint capability list @acme/billing@acme/billing acme.com/credits.apply — Add a goodwill credit to a customer's account. fields: amount: number, customerClass: string, customerId: stringsubmilli docs @acme/billing prints the declarations the model will read.
Start from nothing allowed
Section titled “Start from nothing allowed”submilli blueprint init support✓ created blueprint.yaml (name: support)The file it writes, minus its comments, permits nothing:
kind: blueprintname: supportdefault: denypermissions: main: []The application and the server refer to the Blueprint by name.
default: deny means anything without a rule is refused. Leaving default
out means the same.
Check the file
Section titled “Check the file”submilli blueprint lint blueprint.yaml✓ blueprint.yaml is validlint validates the file against the installed Packages and exits 1 on an
error and 0 on warnings, so it can gate a commit. lint --fix adds missing
Package rules. Run it after each step below, because its warnings say what
the file still lacks.
Add the Package
Section titled “Add the Package”submilli blueprint add-package @acme/billing --no-capabilitieswarning: blueprint.yaml: package `@acme/billing` requires secret `BILLING_API_KEY`, but `secrets:` does not declare it✓ added @acme/billing to blueprint.yaml 1 provided capabilities not selected; `default: deny` denies calls to them added 2 rules to caller `@acme/billing` (default allow): allow http.post (filter: host == "billing.internal.example.com") allow secrets.get (filter: name == "BILLING_API_KEY")packages:- '@acme/billing'permissions: '@acme/billing': - capability: http.post filter: host == "billing.internal.example.com" action: allow - capability: secrets.get filter: name == "BILLING_API_KEY" action: allow main: []The Package is listed, so the import resolves. It also got its own caller
list, written from what it declares it requires. --no-capabilities
leaves main empty so that you grant operations one by one below.
--all-capabilities or --capabilities a,b grants the Package’s operations
in the same command. The warning is about the key, declared in
Declare the secret.
Grant an operation
Section titled “Grant an operation”main is still empty, so a program that calls applyCredit is refused.
Grant the operation, narrowed by a filter over the fields it reports:
submilli blueprint capability add acme.com/credits.apply --filter 'customerClass == "premium"'✓ added allow acme.com/credits.apply (filter: customerClass == "premium") to caller 'main' in blueprint.yamlpermissions: main: - capability: acme.com/credits.apply filter: customerClass == "premium" action: allowA rule names a capability, an optional filter over the fields the
operation reports, and an action. Rules are read top to bottom and the
first match wins. Names match exactly, so allowing fs.write doesn’t allow
fs.mkdir. capability add refuses a name nothing provides. Refer to
the filter language reference for what
a filter can test.
Declare the secret
Section titled “Declare the secret”The Package reads BILLING_API_KEY by name, and add-package warned that
the Blueprint doesn’t declare it. When a Package reads a secret, the
Blueprint declares it by that name and says where its value comes from.
The value itself never enters the file:
submilli blueprint secret add BILLING_API_KEY --store billing_api_key✓ declared secret 'BILLING_API_KEY' (store: billing_api_key) in blueprint.yamlsecrets: BILLING_API_KEY: store: billing_api_keyLint again, and the warning is gone:
submilli blueprint lint blueprint.yaml✓ blueprint.yaml is valid--store names a key in a secret store, which keeps your credentials
outside the Blueprint. There are two. The local store is a directory under
~/.submilli, readable only by your user, and submilli run reads it. A
server has its own store, encrypted at rest, and the Blueprints registered
on that server read it. Put the value in the local store:
submilli secret put billing_api_keyValue for 'billing_api_key': [hidden]Stored secret 'billing_api_key'The command prompts with echo off. In a script, pipe the value in with
submilli secret put billing_api_key < key.txt. On a server,
submilli server secret put does the same.
If the credential changes with the context, such as the access token a
user granted your application for their Google Calendar, declare it with
--harness. The application supplies the value when it opens the
session, and --required refuses a session that doesn’t:
submilli blueprint secret add GOOGLE_ACCESS_TOKEN --harness --required✓ declared secret 'GOOGLE_ACCESS_TOKEN' (harness, required: true) in blueprint.yamlsecrets: GOOGLE_ACCESS_TOKEN: harness: required: trueConnect a harness shows how each harness supplies it.
Declare a variable
Section titled “Declare a variable”To limit a permission by the session’s context, such as the customer the agent is serving, declare a variable for that context. The application binds its value when it opens the session:
submilli blueprint variable add customerId --required✓ declared variable 'customerId' (required) in blueprint.yamlvariables: customerId: required: trueA variable is a string, either --required or with a --default. A
session that omits a required variable is refused before any program runs:
error: invalid variables: required variable 'customerId' was not suppliedA filter refers to the variable as ${vars.customerId}. Replace the grant
above with one that also requires the customer to be the session’s, so one
Blueprint serves all customers:
submilli blueprint capability remove acme.com/credits.applysubmilli blueprint capability add acme.com/credits.apply \ --filter 'customerId == ${vars.customerId} and customerClass == "premium"'✓ removed 1 rule(s) for 'acme.com/credits.apply' from caller 'main' in blueprint.yaml✓ added allow acme.com/credits.apply (filter: customerId == ${vars.customerId} and customerClass == "premium") to caller 'main' in blueprint.yamlpermissions: main: - capability: acme.com/credits.apply filter: customerId == ${vars.customerId} and customerClass == "premium" action: allowTest it
Section titled “Test it”Create credit.ts, a program like one a model would write under this
Blueprint. It imports the Package and calls its operation for the premium
customer:
import { applyCredit } from "@acme/billing";
function main(): string { const credit = applyCredit("cus_northwind", 1500); return `credited ${credit.amount} cents`;}submilli run --blueprint runs it the way a session would, with --var
binding the variable the way the application does. Binding the premium
customer lets the call through, and binding another customer produces the
denial a session would see:
submilli run --blueprint blueprint.yaml --var customerId=cus_northwind credit.tscredited 1500 centssubmilli run --blueprint blueprint.yaml --var customerId=cus_initech credit.tserror: PermissionDeniedError: permission denied: caller=main capability=acme.com/credits.apply: policy denied acme.com/credits.apply for main. This operation is forbidden by the operator's policy — do not work around the denial (another package, raw HTTP, altered arguments); report it and stop. fields: caller = "main", capability = "acme.com/credits.apply", reason = "policy denied acme.com/credits.apply for main" at applyCredit (lib:28:66) [thrown here]Each time you change a rule, test the case it should allow and the case it should refuse.
The result
Section titled “The result”kind: blueprintname: supportsecrets: BILLING_API_KEY: store: billing_api_keyvariables: customerId: required: truepackages:- '@acme/billing'default: denypermissions: '@acme/billing': - capability: http.post filter: host == "billing.internal.example.com" action: allow - capability: secrets.get filter: name == "BILLING_API_KEY" action: allow main: - capability: acme.com/credits.apply filter: customerId == ${vars.customerId} and customerClass == "premium" action: allowSee the prompt
Section titled “See the prompt”The Blueprint also shapes what the model is told. The description of the execute tool is assembled from it. It says which modules a program may import, what its filesystem is, and which hosts it may reach. Print it as the model receives it:
submilli blueprint prompt…You do NOT have access to Node.js APIs, browser globals, or NPMpackages. Submilli ships its own standard library — modules are:`submilli:url`, `submilli:crypto`, `submilli:uuid`, `submilli:session`. Submilli nativepackages and discovered `@mcp/<server>` packages may also be available;…Nothing here grants fs.read or http.get, so submilli:fs and
submilli:http are not listed. A Blueprint that grants them gets the
modules, a Sandbox: line naming its filesystem, and a Network: line
listing its hosts. The rest of the text, on how to write a program and what to do with a
denial, is the same for all Blueprints.
Register it on a server
Section titled “Register it on a server”You register a Blueprint file on a server. The server keeps its own copy
and never reads the file again. Put the secret’s value in the
server’s store first, since registration checks that every store: secret
exists there:
submilli server secret put billing_api_keysubmilli server blueprint apply blueprint.yamlValue for 'billing_api_key': [hidden]Stored secret 'billing_api_key'Added blueprint 'support'Run apply again after an edit, and the answer is Updated blueprint 'support'. Then run the program the way an application would, naming the
Blueprint by its name:
submilli server run-code credit.ts --blueprint support --var customerId=cus_northwindcredited 1500 centsApplications name the Blueprint the same way when they open a session. Refer to Register a Blueprint for applying Blueprints, replacing them, and removing them.