Set up a second, confidential Agent Zero instance
How to run a second Agent Zero container for sensitive work — bookkeeping, personal finance, anything with PII — that uses only local models, does no coding, and never leaks confidential data to the cloud. Includes the confidential agent's task list, its premade scripts, and how it asks the Coder for anything missing.
playbook agent-zero privacy local-first a2a how-to
Set up a second, confidential Agent Zero instance
What this page is for. Stand up a second Agent Zero container that does sensitive work — bookkeeping, personal records, anything with PII — using only local models, doing no coding itself, and never letting confidential data cross to the cloud. It covers both audiences: the owner (Steps 1–6) and the confidential agent (the task catalog at the end).
Your main Agent Zero is a public workspace: it can use cloud models, browse, and code freely. The confidential instance is its opposite — isolated and local-only.
The two-container model
| Container | Confidential data? | Coding? | Models | Role |
|---|---|---|---|---|
| Coder (your main A0) | Never | Yes | cloud OK | builds/fixes tools from public info only |
| Confidential (this page) | Yes — local only | No | local only | does the sensitive work in isolation |
The point of separate containers is blast-radius reduction. A bug, mistake, or prompt-injection in one can't reach the other's data or privileges.
Step 1 — launch a second instance
Run another Agent Zero container (same image, different port + its own usr/ volume). It gets its own settings, memory, projects, and secrets — fully isolated from your main instance.
# illustrative — adjust ports/volumes to your setup
docker run -d -p 8081:80 \
-v a0-confidential:/a0/usr \
agent0ai/agent-zero
Step 2 — local models only
In the confidential instance's Settings → Models, configure only local endpoints (Ollama / LM Studio / llama.cpp). A chat-friendly, tool-calling model like Gemma works well for interactive work; a stronger local model for reasoning. See Configuring profiles for the three roles.
Do not add any cloud API key to this instance. With no cloud credentials, there is no path for sensitive text to reach a provider — even by mistake. This is the single most effective guardrail.
Step 3 — keep it non-coding
The confidential agent reads data and makes decisions; heavy code generation belongs with the Coder. Two reasons: coding quality is best on the strong cloud model this instance deliberately lacks, and it shrinks what a compromised session could do. When it needs a script written or fixed, it uses the Coder-prepares protocol (task catalog below) — sending only redacted samples, never real records.
Step 4 — connect the two with A2A (the safe direction)
Agent-to-Agent (A2A) lets containers talk. The security rule that matters:
The token grants full access to whichever instance exposes its server. So the confidential instance calls out to the Coder, and only the Coder enables its A2A server. You never hand a cloud-connected box a token into your data store.
Setup:
- On the Coder: Settings → MCP/A2A → Enable A2A server → copy the connection URL (
http://HOST:PORT/a2a/t-TOKEN). - On the Confidential instance, store that URL as a secret — Project settings → Sensitive Variables, name it e.g.
CODER_A2A_URL. Secrets are per-project and never appear in chat history or model prompts; reference them later as §§secret(CODER_A2A_URL). - From the confidential agent:
a2a_chat(agent_url="§§secret(CODER_A2A_URL)", message="…").
Gotchas from real setup: inside a container,
127.0.0.1is itself, not the peer — use the other container's name (same Docker network) or its host-mapped address. Secrets added via the UI take effect immediately; editing the file directly won't refresh a running instance until it reloads.
Step 5 — how code and data cross the wire
The confidential side is the caller, so it attaches files — meaning only it can leak, and you control what it sends:
| What moves | How |
|---|---|
| Redacted / synthetic samples → Coder (so it can debug a parser) | The redact_fixture.py script scrubs a real CSV into a shape-preserving fixture; attach that, never the original. |
| Code ← Coder back to you | Returns as the reply's text, or via a git repo you choose (local file server / GitHub / GitLab / Cloudflare). A script lands it — not a model transcribing values. |
There is no shared filesystem between containers by design. Everything crossing the boundary is deliberate and reviewable.
Step 6 — encryption at rest
By default your confidential data sits in its workspace protected by your disk/VM encryption. Encrypting it separately with gocryptfs requires the host to grant the container FUSE access (--device /dev/fuse --cap-add SYS_ADMIN) — a host/orchestration action outside the agent's control, so the instance ships working without it and adopts it when the owner provides both flags. Mount before the container starts; pass the passphrase at startup, never as a keyfile next to the ciphertext.
For the confidential agent — your task catalog
You run inside the isolated container with local models only. The invariant governs every action: no monetary or personal value is ever transcribed by you across a boundary. You may read data to decide (a category, an account mapping, "these rows are a candidate match"); you never copy a figure into a message or file that leaves the container. Amounts move file → code → ledger.
Every task below is either backed by a premade script (already in your workspace toolkit/, deterministic, tested) or uses the Coder-prepares protocol. Log every decision with audit_log.py.
| # | Task | How you do it |
|---|---|---|
| 1 | Categorize a transaction (merchant → expense account) | Read the row, emit a categorical JSON decision (account, confidence). The amount is carried by code from the source field — never re-typed by you. |
| 2 | Produce a safe sample for the Coder | Run toolkit/redact_fixture.py IN.csv OUT.csv. Attach only OUT.csv to your A2A message. It preserves column names + row count and replaces accounts/emails/amounts with typed placeholders. |
| 3 | Ask the Coder to write/fix a parser or script (Coder-prepares protocol) | See below. You never hand over real data; you describe the shape and attach a redacted fixture. |
| 4 | Ingest a CSV into the ledger | Call the deterministic ingest script (provided by the Coder once, then reused). You trigger it; it does the numbers. |
| 5 | Reconcile two sources | Present both figures as-is via code; flag differences for a human to explain. Never average or silently overwrite. |
| 6 | Record any decision you make | toolkit/audit_log.py append --actor bookkeeper --action <task> --decision <value> --inputs <files> — records file hashes, not contents. |
The Coder-prepares protocol (use when no script exists yet)
- Describe the task and the shape of the data (columns, types) — in words, not raw rows.
- Generate a redacted fixture:
toolkit/redact_fixture.py real.csv sample.csv. - Send it:
a2a_chat(agent_url="§§secret(CODER_A2A_URL)", message="<task + shape>", attachments=["sample.csv"]). - The Coder returns code as reply text or via git. Do not run it yet.
- Review the inbound code for exfiltration / supply-chain risk (network calls, env reads, obfuscation). Only run what passes; a script lands files — you never paste confidential values into the request.
- Save accepted scripts under
toolkit/so the task becomes a premade capability next time.
Your premade toolkit (present + tested)
toolkit/redact_fixture.py— stdlib-only CSV scrubber; verified to leave zero account numbers, emails, or amounts in its output while keeping structure.toolkit/audit_log.py— append-only hash-chained JSONL audit trail; verified that editing any earlier line breaks the chain onverify, and appending a new event does not hide the edit.
Anything else (CSV→ledger ingest, OCR extraction, exchange import) is created through the Coder-prepares protocol and then lives in toolkit/ as your own script.
Scope & references
- Tax preparation is out of scope here; it is a separate use case layered on a clean ledger — see the Bookkeeping playbook.
- Model setup for this instance: Give Agent Zero a coding-capable model and Configure profiles.
- Concept behind the isolation: Agent containment.