Imported from bglusman/calciforge (
AGENTS.md). Install upstream withnpx skills add bglusman/calciforge. Copyright stays with the author.
AGENTS.md — Calciforge
Workspace-wide instructions for any AI coding agent (Claude Code, Codex, Copilot cloud agent, OpenClaw, etc.) operating on this repo. Vendor-specific instructions live alongside this file:
CLAUDE.md— Claude Code specifics. Required reading regardless of agent: it carries the public-repo secret-discipline rules (never-commit list, two-layer gitleaks, deployment identifiers). Every agent must follow those rules..github/copilot-instructions.md— GitHub Copilot PR-review tuning..github/instructions/rust.instructions.md— path-scoped (applyTo: "**/*.rs") Rust review specifics; Copilot loads it automatically on Rust diffs.
What this repo is
Self-hosted security gateway between AI agents and the rest of the world. Multi-crate Rust workspace. Substitutes secrets at the request boundary, gates outbound destinations per-secret, scans inbound + outbound traffic, runs a Starlark policy sidecar (clashd), and a separate mTLS daemon (host-agent) for sensitive system operations.
User-facing tour: README.md → calciforge.org.
Crates (workspace members)
| Crate | Role |
|---|---|
calciforge |
Channel router, identity, command dispatch, model gateway. The user-facing binary. |
security-proxy |
HTTPS proxy on 127.0.0.1:8888. Substitutes {{secret:NAME}}, gates per-secret destinations, drives scanning. |
secrets-client |
env → fnox resolver. Default subprocess wrapper around the fnox CLI; opt-in library mode behind --features fnox-library. |
mcp-server |
MCP surface for agent-facing secret-name discovery. Returns {{secret:NAME}} reference tokens; deliberately no get_secret. |
paste-server |
Localhost-only HTTP form for one-shot / bulk .env secret input without putting values in chat history. |
clashd |
Daemon adapter around the upstream clash Starlark policy crate. The "d" is for daemon. |
host-agent |
mTLS RPC server for ZFS / systemd / PCT / git / exec delegation. Has its own crates/host-agent/AGENTS.md with security-model specifics. |
adversary-detector |
Inbound prompt-injection scanning + outbound exfiltration-pattern scanning. |
calciforge-policy-plugin |
Plugin entry point for clashd policy evaluation. |
loom-tests |
Concurrency property tests using loom. |
Project vocabulary (don't rename)
- Calciforge — the project.
- Calcifer — per-agent contract (model, tools, identity, scope).
- Moving Castle — a deployment of Calciforge.
- Doors — channel/identity entry points (chat channel + identity → routing).
{{secret:NAME}}— sentinel string parsed across substitution engine, MCP server, and clashd policies. Don't suggest a typed wrapper; the syntax is a contract.zeroclaw_*— the upstream third-party tool we wrap, NOT pre-rename leftovers from this project.
Mandatory rules for every agent
- Public repo. Read
CLAUDE.mdbefore committing. Never commit deployment-specific identifiers (real domains, dynamic-DNS hostnames, private LAN IPs, real chat handles, hardcoded fallback URLs that disclose infra). - Pre-commit gate is real. It runs
cargo fmt --check,cargo clippy -D warnings, andgitleaks protect --staged. Don't bypass with--no-verify. - Test fixtures with deliberately-fake secrets (
+15555550100,7000000001,eyJ0eXAi…) are allowlisted in.gitleaks.toml. Don't "fix" them. {{secret:NAME}}is a sentinel, not a placeholder to "improve". Touching its parser without touching every consumer (substitution engine, MCP, clashd policies) is a regression.- Substitution boundary order: pre-substitution host extraction → URL substitution (gated by per-secret allowlist) → bypass check → header substitution → body substitution → outbound scan. New code must not move bypass before substitution.
- No secret values in logs. Log the secret name, never the value. URLs containing bearer tokens or short-lived auth go to
debug!, notinfo!/warn!. fnox set <name> <value>leaks viaps/procfs. Use stdin mode (set <name> -+ write to stdin).- Exec-backed model prompts should travel by stdin or secure temp files. Avoid putting prompt or secret-bearing text in argv; process listings can expose it on multi-user systems.
- Bugfixes start with a failing regression test. Before changing behavior, write or identify a test that reproduces the user-visible failure and verify it fails for the right reason. If a reproducing test is impractical, document why in the commit or PR and include the closest executable guardrail.
- Product-contract check before design changes. Before changing installer behavior, gateway routing, secret handling, agent adapters, channel UX, or model selection, stop and ask: does this API or behavior actually fulfill Calciforge's design intent as a self-hosted security gateway, or merely make the local code pass? Preserve central promises such as one operator-owned secret store, agents never receiving plaintext secrets by default, model traffic flowing through the configured gateway unless explicitly opted out, and channel commands behaving consistently across supported transports.
- Cross-node assumptions must be explicit. Do not assume a helper binary, config file, fnox vault, MCP server, or environment variable exists on an agent host just because it exists on the Calciforge host. Multi-node features need an explicit propagation model, a runtime smoke test from the agent host, and docs that name whether state is central or local.
- Avoid accidental architecture drift. If a quick fix creates a second source of truth, bypasses the gateway/proxy, weakens a security boundary, or contradicts a documented roadmap/ADR, treat that as a design bug. Either implement the coherent version or leave a clearly documented follow-up with the user-visible limitation.
- Large files are debt with budgets, not precedent.
scripts/check-architecture-ratchets.rbpins current oversized Rust modules to explicit line budgets and fails CI if they grow. New Rust modules should stay under the default budget unless the PR explains the boundary being created and adds a budget consciously. - Stringly data stays at the boundary. It is acceptable for config, JSON, CLI args, and protocol payloads to enter as
String,Vec<String>, orHashMap<String, String>, but core logic should convert them into typed structs/enums before making security, routing, lifecycle, or persistence decisions. - Detached work needs an owner. New
tokio::spawnor thread-spawned work must have an explicit lifecycle owner, cancellation/error path, and state handoff. Do not update shared mutable state from background tasks unless the owning module documents the ordering and failure behavior. - Work in reviewable story slices. Before starting a broad change, write down the smallest user-visible story or contract being improved. Keep the first patch inside that slice unless the code proves the boundary is wrong. If the task expands, split it into follow-up PRs instead of letting one branch become a second architecture.
- Contracts beat generated volume. AI-generated tests, fixtures, and docs are not evidence by themselves. Every generated artifact must tie back to a precondition, postcondition, invariant, scenario, or operator-visible promise. Remove or rewrite tests that cannot fail for the intended reason.
- Measure before performance fixes. For latency, throughput, model cold starts, lock contention, retry storms, and async task behavior, capture the measurement first. A performance PR should name the baseline, the bottleneck hypothesis, the change, and the post-change measurement.
Build / test
# Workspace-wide
cargo test
cargo build --release
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
# Per-crate
cargo test -p calciforge
cargo test -p calciforge --features tiktoken-estimator
cargo test -p secrets-client
cargo test -p secrets-client --features fnox-library
# Loom (concurrency model checking)
RUSTFLAGS="--cfg loom" cargo test -p loom-tests --release
# Pre-push gate (run before push)
bash scripts/install-git-hooks.sh # one-time
Editions
Mixed: older crates on 2021, newer on 2024. Known and tracked. Don't bump in a PR that isn't explicitly about edition migration.
Documentation standard for channels (and future subsystems)
Channel setup guides live in docs/channels/<channel>.md and are part of the
public docs site (calciforge.org/channels/…).
Every channel doc must have:
- An architecture diagram (ASCII text art showing the message flow)
- Prerequisites section (external accounts, tokens, running services)
- A
[[channels]]TOML config block — this is the source of truth for config examples - An identity/routing TOML block showing how to wire users to the channel
- A verify/health-check step
The TOML blocks are compile-tested. crates/calciforge/src/config.rs contains
test_channel_docs_<channel>_toml_blocks_valid tests that load each markdown file
via include_str!, extract every fenced toml block containing [[channels]],
and parse it against the live CalciforgeConfig schema. If a field is renamed or
removed and the doc isn't updated, cargo test -p calciforge fails.
When adding or modifying a channel:
- Update or create
docs/channels/<channel>.mdwith accurate config examples - Add or update the corresponding
test_channel_config_<channel>_inlineandtest_channel_docs_<channel>_toml_blocks_validtests inconfig.rs - Run
cargo test -p calciforgeto confirm all doc tests pass - Update
docs/index.mdif adding a new channel
When renaming a ChannelConfig field:
- Run
cargo test -p calciforge— the doc-block tests will fail, naming the broken doc - Fix the markdown file, re-run tests, then commit both together
Do not add a new channel without a corresponding docs/channels/<channel>.md.
When working on a specific area, also read
crates/host-agent/AGENTS.md— host-agent security model (Unix-permissions enforcement, fail-closed, mTLS CN→Unix user mapping).docs/rfcs/— design docs for in-flight subsystems (model gateway primitives, secret-input web UI, etc.).docs/security-gateway.md— security-proxy internals.docs/model-gateway.md— Alloy / Cascade / Dispatcher / ExecGateway primitives.