“Where did that token come from?”
Someone pasted it into a chat. Now it sits in a transcript, a log and a model's context.
A ConvOps environment bundles what an agent run needs: repositories, git access, secrets, MCP servers, allowed tools and a commit identity. A check proves it works before any run.
secrets by one-time link · masked in every log · checked before any run
Define it once in ConvOps: the repositories, the secrets, the MCP servers and which of their tools a run may use. Every run then starts from the same checked setup.
ConvOps is the operations layer for AI agents: an MCP server that holds your team's process as workflows, with approval gates, a shared memory and an audit trail. It runs no AI models.
updated
Agents need repositories, keys and tools. Without a shared setup, each of those lives somewhere nobody governs.
“Where did that token come from?”
Someone pasted it into a chat. Now it sits in a transcript, a log and a model's context.
“What happens when someone leaves?”
Repo access, keys and config lived on one laptop. The agent stops working the day they do.
“Why could the agent delete issues?”
It got every tool the server offers, because nobody decided which ones it should have.
A release bot gets its repository, a secret, a tool server and its tools. Then a check proves it, before any real work.
Name web-app and add a deploy key for it. When several git access rules match an address, the most specific one supplies the credential.
git access · which credential clones it?
longest matching prefix wins · one rule covers https, ssh and git@
ConvOps issues a one-time link. A teammate posts the value there. It never enters a chat, and it is never shown again.
value posted once, never shown again
The GitHub MCP server starts as a sidecar inside each run pod. Two of its tools are denied, so the run never sees them.
Tasks and memory on. Secrets and schedules off. A person picks the list, and the run is offered exactly that.
task tools reach the run's own task · widening one saves with a warning
The environment check clones every repository, resolves every secret and probes every variable in a throwaway copy. Nothing is pushed.
Make it the project default. Each run starts in its own pod with repositories cloned, secrets in place and only the allowed tools.
git access · which credential clones it?
longest matching prefix wins · one rule covers https, ssh and git@
A one-time link takes the value. Each run gets it in its own secret. Logs and transcripts show a lock, never the value.
value posted once, never shown again
Allow or deny each MCP tool. Switch ConvOps tools on or off. A run is offered that list and nothing else.
a tool that reaches beyond the run's own task saves with a warning naming what a run can then do
The check clones, resolves and probes in a throwaway copy. A failure names its reason. Fix it, check again, then put it to work.
example failure clone gitlab.com/acme/web-app · no git access for this address →add a deploy key, check again
Cloned into folders of the working copy. Git access maps an address to a deploy key or token.
longest prefix wins
Posted once through a one-time link. Never pasted in chat, never returned, masked in every log.
one-time link
Remote, or a container sidecar inside the run pod. Each tool allowed or denied.
per-tool rules
A person switches each tool on or off. Task tools reach the run's own task by default.
you decide
Every commit a run makes carries the name and email you set, as author and committer.
traceable work
Repositories in order (exactly one receives the work), plain variables, secret refs, MCP servers with tool rules, ConvOps tools and a commit identity. Values are never accepted.
// agent_environments_manage · example
{
"action": "create",
"name": "release-bot",
"entries": [
{ "repository_id": "web-app", "folder": ".", "receives_work": true },
{ "repository_id": "design-tokens", "folder": "vendor/tokens", "shallow": true }
],
"variables": [{ "name": "NODE_ENV", "value": "test" }],
"secrets": [
{ "name": "STRIPE_KEY", "secret": { "source": "intake" } },
{ "name": "SENTRY_TOKEN", "secret_ref_id": "sr_4b81" }
],
"mcp_servers": [
{ "server": "github",
"rules": { "default": "allow",
"deny": ["delete_*", "merge_pull_request"] } }
],
"convops_tools": ["tasks_get", "task_notes_add",
"workflow_advance", "memory_recall", "memory_store"],
"git_identity": { "name": "release-bot", "email": "release-bot@example.com" }
}Every secret added with source "intake" comes back as a one-time link for a person to post the value. The ref stays unset until then.
// result (excerpt) · example
{
"intake": [{
"name": "STRIPE_KEY",
"url": "https://app.example.com/intake/…",
"command": "curl -X POST … --data-binary @-",
"expires_at": "2026-10-03T18:00:00Z"
}]
}One prefix covers https, ssh and git@ forms and matches whole path segments. The most specific prefix supplies the credential.
// git_access_manage · example
{ "action": "create", "prefix": "gitlab.com/acme",
"kind": "token", "username": "oauth2",
"secret": { "source": "intake" } }
{ "action": "create", "prefix": "gitlab.com/acme/web-app",
"kind": "ssh_key",
"secret": { "source": "intake" } }
// cloning gitlab.com/acme/web-app uses the deploy key:
// the longest matching prefix winsA container server starts beside the agent in the run pod and is reached on localhost. Credentials go in secret env, never in the address.
// mcp_servers_manage · example
{
"action": "create",
"name": "github",
"transport": "container",
"image": "ghcr.io/example/github-mcp:1.4.2",
"port": 8080,
"env": [{ "name": "GITHUB_TOKEN", "secret": { "source": "intake" } }],
"rules": { "default": "allow", "deny": ["delete_*"] }
}
// started beside the agent in the run pod,
// reached on http://127.0.0.1:8080/mcpEach repository ready or failed with a reason, each variable present or empty. Secret values are never echoed. Nothing is pushed.
// environment_checks_submit · result · example
{
"status": "succeeded",
"terminal": true,
"output": {
"repositories": [
{ "folder": ".", "address": "gitlab.com/acme/web-app",
"ref": "main", "head": "9f2c1e7", "status": "ready" },
{ "folder": "vendor/tokens", "address": "gitlab.com/acme/design-tokens",
"ref": "main", "head": "41ab0d3", "status": "ready" }
],
"variables": [
{ "name": "NODE_ENV", "kind": "plain", "status": "present", "value": "test" },
{ "name": "STRIPE_KEY", "kind": "secret", "status": "present" },
{ "name": "SENTRY_TOKEN", "kind": "secret", "status": "present" }
]
}
}A value is posted through a one-time link, or set by a signed-in person in the app. No ConvOps tool or API returns it. A run receives it in its own per-run Secret, and it is masked in every log and transcript. Self-hosted, a secret can also name a Kubernetes Secret in your runs namespace, which the run pod reads directly.
No tool accepts a value. When a secret is added, ConvOps returns a one-time link for a person to post it, so the value never passes through a conversation. Values must be at least 8 characters.
Git access maps an address prefix to a deploy key or token. When a repository is cloned, the longest matching prefix supplies the credential, so one token can cover an organisation and one deploy key can override it for a single repository.
Yes. Each MCP server attached to an environment has rules: a default of allow or deny, then deny patterns, then allow patterns. A run is offered only the tools those rules let through. Servers can be remote, or run as container sidecars inside the run pod.
The ones the environment switches on. Task tools reach only the run's own task unless you widen them. Granting a tool that reaches further, such as secrets or schedules, saves with a warning that names what a run can then do.
It prepares the environment in a throwaway copy: resolves every secret, clones every repository, probes that each variable arrives, reports each one as ready or failed with the reason, then removes the copy. It never pushes.
The commit identity set on the environment, as author and committer. Without one, the workspace default applies. Work is pushed to a task branch for review, or straight to the ref when you configure it.