Jern Cloud docs
Policy.
Policy is enforced by the open-source runtime before the agent acts, pinned by digest for the life of a session, and checked again by the runner before anything is published.
Two sources, one winner
A session takes its policy from one of two places. A repository baseline is a file in the repository, .jern/baseline.json, protected by your branch rules and reviewers. A workspace default profile is a policy an admin approved under Settings › Policy for repositories that have no baseline yet. If the repository has a baseline, it always wins; the profile is a way to start before the setup pull request is merged, not a way around it.
What a baseline says
{
"policy": {
"allow": ["edit_file", "write_file"],
"deny": ["mcp__*"],
"edits_within": ["src/", "tests/"],
"memory": "deny",
"shell_allow": ["python3 -m unittest discover -s tests -t ."]
}
}
edits_within: the only paths the agent may change. The runner also refuses to publish a change outside them.shell_allow: commands that run without asking. Anything else needs approval, and in a cloud session there is nobody to ask, so it is denied.allowanddeny: tool grants and refusals, with globs such asmcp__*.memory: whether the agent may read or write its persistent memory. Jern Cloud keeps that memory per repository between attempts, encrypted, restored into.jern/memory.jsonbefore the agent runs and written back after; it never reaches a commit and never crosses repositories. Cloud sessions have nobody to ask, so only"allow"lets the agent use it; the agent reads and writes it with thememory_readandmemory_writetools. The repository page shows it decrypted, and anyone who can start sessions there can edit or delete it, since it is text the agent wrote to itself.
Services
The baseline has two sections. policy is what the runtime enforces on the agent itself, the same on a laptop, in the Action, and in the cloud. environment is what the host must provide around the agent; the runtime recognises it and applies none of it, and Jern Cloud grants it from a catalog. Services are the first environment declaration, for example a database the tests need:
{
"policy": { "edits_within": ["src/", "tests/"], "shell_allow": ["dotnet test"] },
"environment": { "services": ["postgres:16"] }
}
A declaration is a ceiling, not a schedule. Each declared service runs on its own machine, created when the agent asks for it and destroyed with the attempt, with credentials minted for that attempt. The agent asks by calling start_service on the run-scoped jern MCP server, which the runner adds to the runtime's toolset as mcp__jern__start_service; the call returns once the service accepts connections, and an attempt that never needs the database never starts one. Because the runtime asks before every MCP call unless the policy grants it, a baseline that declares a service allows the tool, and no layer may deny it: a deny of mcp__* in the repository's own jern.json wins over the baseline's allow, since restrictions compose downward.
{
"policy": { "allow": ["edit_file", "write_file", "mcp__jern__start_service"] },
"environment": { "services": ["postgres:16"] }
}
The agent reaches the service at 127.0.0.1 on the service's port, in both the setup and the agent phase, through a relay into its private network namespace; nothing listens there until the ask has returned, nothing else about the agent's network changes, and the credentials never reach a database or a receipt. The session header names the declared services, the attempt shows which of them were started, the trace carries the ask, and the audit log records it.
Blast radius
Beside edits_within, a baseline can bound how much a run may change: protected_paths no edit may touch whatever else allows it, max_files_edited for the most distinct files a run may edit, and max_lines_changed for the most lines that may change, a replaced region counting its old lines plus its new ones. The runtime enforces them as each edit is attempted, the tightest source wins, and a denied edit tells the agent the source and the total it would have reached. The session card shows the limits with the policy, and the receipt reads them against what happened: 2 of at most 5 files, 16 of at most 200 lines changed.
"policy": { "edits_within": ["src/", "tests/"], "protected_paths": [".jern/", ".github/"], "max_files_edited": 4, "max_lines_changed": 200 }
Issues, pull requests, and check logs
The agent can read the repository's issues, its pull requests, and the log of a failing check on its own pull request without holding a GitHub credential. Four tools on the same run-scoped server, read_issue, list_issues, read_pull_request, and read_check_log, cross to the control plane, which reads with a token that carries only the read permission the ask needs on that one repository and never enters the machine, and answers as text: an issue's title, labels, description, and first fifty comments; a pull request's files, checks, reviews, and comments on the diff; the last part of a check's job log, where the failure is. The baseline allows them like any tool:
"policy": { "allow": ["edit_file", "write_file", "mcp__jern__read_issue", "mcp__jern__read_pull_request", "mcp__jern__read_check_log"] }
Nothing is written; the agent cannot comment, label, review, or close anything. Every read is listed on the receipt, issues by number and pull request context by label. A session asked by the failed-check trigger to fix a check can read the failure it is asked to fix.
Reading an allowed page as text
A host on network_allow can also be read through fetch_page on the run-scoped server: the control plane fetches the https page on the public internet only, renders it to text with scripts, styles, and markup gone, and answers with at most thirty thousand characters, so documentation costs a fraction of the tokens raw HTML does. The host must match the allowlist exactly, the address it resolves to must be public, and redirects are followed only to allowed hosts. Each fetch is counted per host on the receipt beside the proxy's own counts. The baseline allows it as mcp__jern__fetch_page.
Stopping to ask
An agent that cannot answer a question from the repository, the issue, or the task can stop and ask instead of guessing. ask_human on the same run-scoped server takes one question and ends the attempt: what the agent has is published as it stands, the question is the agent's turn in the session's transcript and a workspace notification, the receipt says Stopped to ask, and the next message on the session answers it, with the question attached so the next attempt knows what the answer is about. The baseline allows it like any tool, mcp__jern__ask_human; a workspace that prefers guesses leaves it out.
| Declaration | Port | Variables the agent sees |
|---|---|---|
postgres:16, postgres:17 | 5432 | DATABASE_URL, PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE |
redis:7 | 6379 | REDIS_URL |
A declaration outside this catalog stops the session from being created. An organization's default policy profile cannot declare services yet.
Network
The agent phase reaches the model gateway and nothing else, unless the baseline opens exact public hosts to it:
{
"policy": { "edits_within": ["src/", "tests/"], "shell_allow": ["python3"] },
"environment": { "network_allow": ["docs.python.org", "api.stripe.com"] }
}
Lowercase DNS names only, at most sixteen, no wildcards or addresses, and nothing that names the platform itself. Each is reachable over HTTPS on port 443 through the runner's proxy, with private ranges still denied by address. The runner counts every connection to an allowed host from the proxy log and reports it; the attempt shows how many allowed hosts were reached and how often, and the workspace audit log keeps the same. The list is pinned with the policy for the life of a session.
The setup pull request
From Repositories, Jern Cloud can open a pull request that adds a least-authority baseline to a repository. It never merges it. Once merged, the baseline governs every session on that repository, and its digest appears in each session header.
How the runner checks it
Before the agent starts, the runner reads the baseline from the pinned checkout, computes its digest, and refuses to run if it differs from the one the session pinned. After the agent finishes, the runner verifies that the repository's .git tree is untouched, that every changed path is inside edits_within, and that no live credential appears in the tree, and only then obtains a publication token.
The repository's own jern.json
A repository may also carry a jern.json with a test command, further restrictions, and a call budget for the runtime. It can narrow what the baseline allows but never widen it, and its budget is separate from the session's token cap, which the gateway enforces.