How Policy Works
What Levee policy is, where it lives, how it reaches running projects, and how local and cloud policy combine
Policy decides what the AI agent in a Levee project may reach: which websites, which AI models, which databases, which files and which tools. Your IT team writes it, and Levee enforces it outside the agent's sandbox. The agent can neither read nor change it.
By default nothing is allowed. A new machine with no policy is safe but not yet useful: the agent cannot reach the web, call a model, or query a database until policy allows it.
This page explains the model. The pages that follow cover each channel in detail.
Where does policy live?
Each computer has one policy file:
%ProgramData%\Vediram\Levee\policy.json
It gets there in one of two ways:
| Source | How it arrives | Typical use |
|---|---|---|
| Vediram Portal (cloud) | The Levee app downloads your organization's policy as the signed-in user, and the Levee service writes it to policy.json. See Managing Policy in the Vediram Portal. | Computers connected to Vediram Cloud |
| Local file | An administrator writes policy.json directly, or deploys it with Intune, Configuration Manager, Group Policy preferences, or any file-distribution tool. | Offline machines, pilots, machines not connected to Vediram Cloud |
The installer does not create a policy.json. Until one exists, Levee uses its built-in defaults, which refuse every web, model, database, search and Office request.
Only administrators should be able to write to
%ProgramData%\Vediram\Levee\policy.json. Standard users need no access to it; the Levee app reads the policy in force through the Levee service.
What does policy control?
policy.json is a single JSON object. Each top-level key is a section. The proxies section holds one sub-section per channel, that is, one kind of thing the agent can reach.
| Section | Controls | Reference |
|---|---|---|
proxies.http | Websites and online services the agent may reach | Web Access |
proxies.llm | Which AI models the agent may use, through which provider, with which budgets | Model Access |
proxies.odbc | Database queries | Database Access |
proxies.search | Web search (Preview) | Web Search |
proxies.m365 | Word, Excel, PowerPoint and Outlook operations (Preview) | Microsoft 365 |
proxies.pkg | Python and JavaScript package installs (Preview) | Packages |
proxies.fs | Files and folders outside the project | File Access |
proxies.mcp | Plugins that give the agent extra tools (Preview) | Plugins |
container | Memory, environment variables, sandbox image, default agent | Container Settings |
runtimeProvisioning | Where and how the sandbox image is downloaded | Container Runtime |
webIdentity | Whether users may sign the agent in to websites | Connected Identities |
telemetry | Whether audit events are sent to Vediram Cloud | Activity & Audit |
Which rules apply to every policy?
These rules hold for every section.
Default deny. A channel with no section in the policy is closed. A missing proxies.http section behaves exactly like { "default": "deny", "rules": [] }. Nothing is reachable unless a rule allows it.
Fail closed. If a channel cannot enforce policy (for example, its component did not start), the agent does not get open access instead. The request is refused, or the project refuses to start.
Last good policy wins. If policy.json is missing, empty, or contains invalid JSON, the Levee service keeps the policy it already had and logs a warning. A typo in a file you are editing never opens access. If no valid policy was ever loaded, the built-in deny-all defaults apply.
Each section is checked on its own. Each channel validates its own section when it receives it. A section that fails is rejected, and the channel keeps its previous rules (or refuses everything if it never had any). For example, a model-access section that contains an API key is rejected as a whole.
The same refusal every time. When a request is refused, the agent receives the same generic answer whatever the reason, so it cannot work out your policy by trial and error. The reason is recorded in the audit trail. See Agent gets "Access denied.".
Every decision is audited. Allowed, refused, and approved by a person: each decision is recorded in the audit trail. See Activity & Audit.
When do changes take effect?
Most changes apply within seconds, without restarting anything. The Levee service watches policy.json and, when it changes, reloads it and sends the new rules to the running project. This covers:
- web access rules
- model access, database, search and Office rules
- plugins added, updated or removed (once plugins are turned on, see Plugins)
A few settings are fixed when a project's sandbox is created. They apply the next time the project starts:
container.memoryLimitMbcontainer.environmentVariablescontainer.defaultAgentTool(for new chat sessions)
container.baseImage decides which sandbox image the machine downloads. See Container Settings.
For your IT team: reload and inspect
To force a reload (for example after restoring a file with an older timestamp):
levee policy reload
To see the policy the service is actually enforcing:
levee policy show
levee policy show --json | Out-File effective-policy.json
How do local and cloud policy combine?
When a computer is connected to Vediram Cloud, the cloud policy is merged into the local file. Web access always comes from the cloud. Any other section the cloud policy contains replaces the local one. Sections the cloud policy leaves out stay as they are locally.
The Levee app checks for policy updates when it starts, when it reconnects to the Levee service, every 5 minutes, and when the user clicks Sync now in Settings. The merge rules in detail:
- Web access is always taken from the cloud policy. The
proxies.httpsection is rebuilt from the cloud document every time, and refuses everything if the cloud document has no web rules. Local edits toproxies.httpon a cloud-managed machine are overwritten at the next sync. - Any other section present in the cloud policy replaces the local one. For example, if the cloud policy has a
containersection, the localcontainersection is replaced. - Any section absent from the cloud policy is left as it is locally. You can, for example, manage model access centrally while keeping machine-specific database rules in the local file.
- Within
proxies, replacement is per channel. A cloud policy that carries onlyproxies.searchreplaces the local search section and leaves the localproxies.odbcsection untouched.
If a sync fails (the computer is offline, the user is signed out, the service rejects the document), the policy already on disk stays in force. A partly downloaded policy is never applied.
Applying a cloud-format policy by hand
For offline machines, or to test a policy before publishing it, you can apply a cloud-format policy document (the AgentPolicy format the Portal produces) from a file. The same merge rules apply:
levee policy sync --file .\agent-policy.json
A minimal cloud-format document looks like this:
{
"apiVersion": "policy.vediram.com/v1alpha1",
"kind": "AgentPolicy",
"metadata": { "name": "finance-default" },
"spec": {
"targets": ["levee"],
"egress": {
"default": "deny",
"allow": ["api.anthropic.com", "cdn.anthropic.com", "learn.microsoft.com"]
},
"levee": {
"network": {
"rules": [
{ "domain": "*.example-data-provider.com", "ports": [443], "action": "allow" }
]
},
"container": { "memoryLimitMb": 8192 },
"proxies": {
"llm": { "default": "deny", "budgets": { "perProjectDailyTokens": 2000000, "perProjectSessionTokens": 500000 }, "providers": { "anthropic": {} }, "models": [ { "name": "claude-sonnet-4-5", "provider": "anthropic", "upstreamModel": "claude-sonnet-4-5" } ] }
}
}
}
}
In a cloud-format document:
spec.egress.allowis a list of bare host names. Each becomes an allow rule on port 443.spec.egress.defaultsets the default (denyunless set toallow).spec.levee.network.default(allowordeny), if present, overridesspec.egress.default.spec.levee.network.rulesuses the full web access rule syntax. A rule for a host that also appears inegress.allowreplaces the simple allow rule for that host.spec.levee.container,spec.levee.proxies,spec.levee.runtimeProvisioning,spec.levee.webIdentityandspec.levee.telemetryuse exactly the same keys as the correspondingpolicy.jsonsections.spec.pluginslists the plugins your organization enabled. Levee keeps it separately frompolicy.json. See Plugins.
A complete example
The following local policy.json is for a finance team. It lets the agent read a few public reference sites, use Claude models through Anthropic with daily budgets, run read-only queries against one reporting database, and search the web with the user's approval for each query. JSON does not allow comments; the explanations follow the example.
{
"container": {
"memoryLimitMb": 8192,
"defaultAgentTool": "claude-code",
"environmentVariables": {
"PYTHONUTF8": "1"
}
},
"proxies": {
"http": {
"default": "deny",
"rules": [
{ "domain": "api.anthropic.com", "action": "allow" },
{ "domain": "cdn.anthropic.com", "action": "allow" },
{ "domain": "learn.microsoft.com", "action": "allow" },
{ "domain": "www.ecb.europa.eu", "action": "allow" },
{ "domain": "*.wikipedia.org", "action": "escalate" }
]
},
"llm": {
"default": "deny",
"budgets": {
"perProjectDailyTokens": 2000000,
"perProjectSessionTokens": 500000,
"perProjectMaxConcurrentRequests": 4
},
"providers": { "anthropic": {} },
"models": [
{ "name": "claude-sonnet-4-5", "provider": "anthropic", "upstreamModel": "claude-sonnet-4-5" },
{ "name": "claude-opus-4-1", "provider": "anthropic", "upstreamModel": "claude-opus-4-1", "promptOnFirstUse": true }
]
},
"odbc": {
"default": "deny",
"rules": [
{
"alias": "reporting",
"hostDsn": "DSN=Reporting-ReadOnly",
"allowedVerbs": ["SELECT"],
"maxRowsPerQuery": 1000,
"queryTimeoutSeconds": 30,
"tables": { "allow": ["sales.*"], "deny": ["sales.salaries"] }
}
]
},
"search": {
"enabled": true,
"defaultProvider": "tavily",
"allowedProviders": ["tavily"],
"maxQueriesPerSession": 50,
"approval": { "mode": "per-query" },
"providerKeys": { "tavily": "LEVEE_SEARCH_KEY_TAVILY" }
}
},
"webIdentity": { "enabled": false },
"telemetry": { "forwardingEnabled": true }
}
What this policy does:
container: each sandbox may use up to 8 GB of memory, the chat starts Claude Code by default, and one extra environment variable (Python's UTF-8 mode) is set inside every sandbox.proxies.http: everything is refused except the listed sites. Claude Code can reach Anthropic. Two reference sites are allowed. Requests to Wikipedia pause and ask the user. See Web Access.proxies.llm: the agent may use two Claude models. The more expensive one asks the user the first time a project uses it. Each project is capped at 2 million tokens per day and 500,000 tokens per run. The Anthropic key is stored on the computer, never in the file. See Model Access.proxies.odbc: the agent sees one data source namedreporting, can only runSELECT, never sees the salaries table, and receives at most 1,000 rows per query. See Database Access.proxies.search: the agent gets a web search tool backed by Tavily, and the user approves each query before it leaves the computer. See Web Search.webIdentity: users cannot sign the agent in to websites with their own browser session.telemetry: audit events are sent to Vediram Cloud so they appear in the Portal (the computer must be connected to Vediram Cloud).
Checking your work
-
Check that the file is valid JSON before deploying it:
Get-Content "$env:ProgramData\Vediram\Levee\policy.json" -Raw | ConvertFrom-Json | Out-Null -
Confirm the service loaded it:
levee policy show -
Start a project and watch the decisions arrive in the app's Activity tab, or from the command line:
levee audit tail --follow
levee config validatechecksconfig.json(the logging configuration), notpolicy.json. See Logging.