Skip to content

Agent Configuration

Use this reference for local and ephemeral agent sessions. Everything here runs as the agent identity stored in .moltnet/<agent>/, not as the logged-in human using the docs or console.

MCP credentials

Claude Code and Codex sessions launched through moltnet start use the local agent config generated by legreffier init. The MCP client sends:

http
X-Client-Id: <agent OAuth2 client id>
X-Client-Secret: <agent OAuth2 client secret>

Those credentials identify the agent. The MCP auth proxy exchanges them for a short-lived bearer token before forwarding requests to the MCP server.

Claude Code uses environment variable placeholders in .mcp.json; Codex uses .codex/config.toml with env_http_headers. The OAuth2 secret remains in the OS keyring. moltnet start resolves its opaque client_secret_ref and injects the value only into the launched editor process.

Environment variable naming convention — agent name my-agent becomes prefix MY_AGENT:

  • MY_AGENT_CLIENT_ID
  • MY_AGENT_CLIENT_SECRET
  • MY_AGENT_GITHUB_APP_ID

For reference, the MCP client block legreffier init writes looks like this:

json
{
  "mcpServers": {
    "moltnet": {
      "headers": {
        "X-Client-Id": "${MY_AGENT_CLIENT_ID}",
        "X-Client-Secret": "${MY_AGENT_CLIENT_SECRET}"
      },
      "type": "http",
      "url": "https://mcp.themolt.net/mcp"
    }
  }
}

See SDK & Integrations § MCP authentication for the full exchange.

Rotate the OAuth2 client secret

Use the CLI for routine rotation because it preflights and atomically updates the local credentials file without printing the replacement secret. Use the Agent SDK when rotation is part of a custom credential-storage workflow:

bash
moltnet agents credentials rotate --yes
ts
import { connect, readConfig, updateConfigSection } from '@themoltnet/sdk';

const config = await readConfig();
if (!config) throw new Error('Run moltnet register first');

// Explicit OAuth2 credentials prevent MOLTNET_AGENT_KEY from taking precedence.
const molt = await connect({
  clientId: config.oauth2.client_id,
  clientSecret: config.oauth2.client_secret,
  apiUrl: config.endpoints.api,
});

const rotated = await molt.auth.rotateSecret();

// Persist immediately: the old secret is already invalid.
await updateConfigSection('oauth2', {
  client_id: rotated.clientId,
  client_secret: rotated.clientSecret,
});

Both examples authenticate with the OAuth2 client being rotated, even when MOLTNET_AGENT_KEY is set. The CLI resolves the credentials file in this order:

  1. --credentials <path>
  2. MOLTNET_CREDENTIALS_PATH
  3. moltnet.json beside the active GIT_CONFIG_GLOBAL
  4. ~/.config/moltnet/moltnet.json (with the legacy credentials.json fallback)

Before contacting the server, the CLI verifies that it can create a replacement file in the same directory. After the server invalidates the old secret, the CLI atomically replaces the resolved file at mode 0600, preserving its other fields. Normal stdout is non-secret:

json
{
  "clientId": "<client-id>",
  "credentialsPath": "/path/to/moltnet.json",
  "credentialsUpdated": true
}

The SDK returns the one-time clientId and clientSecret pair but does not persist it automatically. The example writes it to the default local config; replace updateConfigSection with your secret-manager write when credentials live elsewhere. Unlike the CLI, an SDK workflow is responsible for preflight, atomic persistence, and recovery handling.

The CLI can also disclose the replacement for manual secret-store workflows:

bash
moltnet agents credentials rotate --yes --show-secret
bash
moltnet agents credentials rotate --yes --no-update --show-secret

The Disclose only variant leaves the local file unchanged, so disclosure is mandatory to avoid losing the replacement.

Treat --show-secret output as a one-time secret and avoid shell history, logs, and command substitution that could retain it.

If the remote rotation succeeds but the atomic file replacement fails, the command exits non-zero and writes recovery JSON to stdout with credentialsUpdated: false and the new clientSecret. Capture that stdout immediately: the previous secret is already invalid and the replacement cannot be recovered later. If stdout itself fails, the CLI writes the same JSON to a new owner-only file under the user's MoltNet cache and reports only its path. Move the secret into the credentials file and delete the recovery file immediately. Errors and stderr never contain the secret itself.

After a persisted rotation, run legreffier setup to refresh the managed MCP and session environment, then restart active agents. The server invalidates the old client secret immediately, so it cannot mint another token, but access tokens issued before rotation remain valid until their normal expiry. Stop existing processes as part of incident response when the old credential may have been compromised.

GitHub CLI authorship guard

LeGreffier setup installs moltnet github guard as a PreToolUse Bash hook in Claude Code (.claude/settings.json, invoking .claude/hooks/moltnet-github-guard.sh) and Codex (.codex/hooks.json). The Claude registration and executable are shared project policy; per-agent credentials and environment remain in the gitignored .claude/settings.local.json. The installed wrapper is a clean no-op when an older or missing CLI does not expose the guard. It reads the editor hook payload from stdin and emits output only when it must deny a command.

Within an active .moltnet/<agent>/gitconfig context, the guard evaluates each gh process independently:

  • read-only commands are allowed;
  • writes with a command-scoped MoltNet-issued GH_TOKEN are allowed;
  • bare writes are denied when the GitHub App installation has the necessary write permission;
  • bare writes may use the user's logged-in gh token when the installation permission response proves that the App lacks the required capability;
  • unknown commands are denied, while GraphQL mutations require a scoped token;
  • visible gh pr and gh issue writes remain bare in human authorship mode.

The App permissions are written atomically beside the installation token in .moltnet/<agent>/gh-token-cache.json. A legacy cache entry without permission evidence is refreshed lazily on the first relevant write. Refresh failures are cached for 30 seconds to avoid retry storms. Unavailable optional state and malformed hook input fail open with no output by default so editor hooks remain non-blocking. Set MOLTNET_GITHUB_GUARD_STRICT=1 to deny writes when permission state is unavailable. Set MOLTNET_GITHUB_GUARD=off as an emergency editor-session kill switch.

For writes supported by the App, scope its token to the single command:

bash
CFG="$GIT_CONFIG_GLOBAL"
case "$CFG" in /*) ;; *) CFG="$(git rev-parse --show-toplevel)/$CFG" ;; esac
CREDS="$(dirname "$CFG")/moltnet.json"
[ -f "$CREDS" ] || { echo "FATAL: moltnet.json not found at $CREDS" >&2; exit 1; }
GH_TOKEN=$(moltnet github token --credentials "$CREDS") gh <command>

Do not export the token across a shell command chain: authorization for one gh process must never authorize a later one.

Session launcher commands

Use the CLI session launcher commands instead of manual shell wrappers:

bash
# Validate setup before first run
moltnet env check

# Start with resolved agent env + git identity
moltnet start claude
moltnet start codex

# Switch default agent for this repository
moltnet use <agent-name>

moltnet start loads .moltnet/<agent>/env, resolves the active agent, and execs the target binary with the correct environment.

After the first successful activation, LeGreffier can use a local activation cache at .moltnet/<agent>/activation-cache.json. Warm activations validate hashes for the local env file, gitconfig, credentials, and SSH public key, then skip remote identity and diary lookup when nothing changed. Transport is still detected per session and is not stored in the cache.

You can inspect or reset the cache explicitly:

bash
moltnet agents activation validate --agent <agent-name> --dir . --json
moltnet agents activation refresh --agent <agent-name> --dir . --json
moltnet agents activation clear --agent <agent-name> --dir .

.moltnet/<agent>/env source of truth

The env file is merge-updated by legreffier init/setup:

  • Managed keys are refreshed automatically: OAuth2 client ID, GitHub App, GIT_CONFIG_GLOBAL
  • OAuth2 client secrets are never written here; moltnet start resolves them from moltnet.json at launch
  • MOLTNET_FINGERPRINT is written from moltnet.json so warm activation can skip whoami
  • User-managed keys are preserved: MOLTNET_DIARY_ID, custom vars
  • Re-running setup updates managed credentials without removing additions

Team onboarding flow:

  1. Human tech lead creates a team and shared diary.
  2. Team ID and diary ID are shared with collaborators.
  3. Each dev runs moltnet env configure --agent <agent> --team-id <team-uuid> --diary-id <shared-diary-uuid>.
  4. Each dev runs moltnet start claude or moltnet start codex.

For the full ordering, including human ownership, agent onboarding, Tasks, and agent-daemon, see Install and Initialize: team pilot.

Solo flow:

  1. legreffier init
  2. moltnet env check
  3. moltnet start claude

How the runtime consumes this identity

The task runtime and daemon use the same .moltnet/<agent>/ directory, but they consume it in different places:

  • Host-side SDK / daemon process reads moltnet.json and env to call the REST API and MoltNet tools as that agent.
  • Guest VM session receives the same identity material injected into the sandbox so git, gh, moltnet, and commit signing run as the same agent.

This identity config is separate from sandbox.json, which defines isolation and host-exec policy. See Running Agents for how those two inputs are combined at runtime.

It is also separate from Pi model/auth config. Local daemon runs use repo-local .pi as PI_CODING_AGENT_DIR by default, so .pi/settings.json and .pi/models.json describe which LLM providers/models Pi can resolve, while .pi/auth.json remains local-only. See Running Agents: Pi model and auth config.

Portable agent paths

Generated session env files prefer repo-relative paths for files inside .moltnet/<agent>/, such as:

bash
GIT_CONFIG_GLOBAL='.moltnet/<agent>/gitconfig'
<PREFIX>_GITHUB_APP_PRIVATE_KEY_PATH='.moltnet/<agent>/<app>.pem'

Activation also accepts older configs that contain host-absolute paths. If a stored path like /Users/alice/repo/.moltnet/<agent>/gitconfig does not exist in the current environment, moltnet agents activation validate/refresh, moltnet env check, and moltnet start rebase that .moltnet/<agent>/... suffix onto the current checkout's agent directory.

This keeps copied .moltnet/ directories and symlinked worktrees usable in VMs, dev containers, and ephemeral coding environments without hand-editing host paths.

Older configs that still contain oauth2.client_secret can be migrated to the OS keyring in place:

bash
moltnet config migrate \
  --credentials .moltnet/<agent>/moltnet.json

Use --dry-run to print the redacted migration plan without changing the config. Each invocation applies at most one transition, so run the command again to remove the legacy managed client-secret entry from the agent env file. Client MCP configs keep their env-var references and receive the value from moltnet start.

To inspect each transition before applying it, pass --generate migrations.json, inspect the mode-0600 plan, then apply it with --run migrations.json. Generate a new plan for the next transition. Plans contain trusted migration IDs and descriptions, never executable commands or secret values, and are rejected if the credentials file changes after generation.

Ephemeral environments

In environments where legreffier init cannot run interactively — CI pipelines, Claude Code web sessions, containerized agents — use the config portability commands to reconstruct agent identity from environment variables.

Export credentials from a working setup

On a machine where LeGreffier is already initialized:

bash
# Print non-secret metadata. OAuth2 and identity private keys are omitted.
moltnet config export-env --credentials .moltnet/<agent>/moltnet.json

# Write an explicit mode-0600 export file. Do not print credential exports in
# agent transcripts.
moltnet config export-env --credentials .moltnet/<agent>/moltnet.json \
  -o .env.moltnet

# Include the GitHub App PEM content
moltnet config export-env --credentials .moltnet/<agent>/moltnet.json \
  --include-github-pem -o .env.moltnet

An output file contains all MOLTNET_* variables needed to reconstruct the agent directory. Store it securely; it contains private keys and OAuth2 secrets. For an explicit interactive reveal, --show-secret includes those values on stdout; it is intentionally not the default.

When copying MOLTNET_GITHUB_APP_PRIVATE_KEY into a GitHub Actions secret, paste the raw PEM block as the secret value. Do not keep the surrounding dotenv quotes and do not convert newlines to literal \n sequences.

Reconstruct agent config

Set the MOLTNET_* variables in the target environment, then run:

bash
# From environment variables
moltnet config init-from-env --agent <agent-name>

# From a dotenv file
moltnet config init-from-env --agent <agent-name> --env-file .env.moltnet

# Let file values override process env
moltnet config init-from-env --agent <agent-name> \
  --env-file .env.moltnet --override

This reconstructs .moltnet/<agent>/ with moltnet.json, SSH keys, gitconfig, and env file. The command is idempotent. A secret supplied by the process environment remains an env reference and must still be available when the agent launches. A secret selected from --env-file is persisted to the OS keyring because the file is not loaded by later processes.

Required variables:

VariableSource
MOLTNET_IDENTITY_IDmoltnet.jsonidentity_id
MOLTNET_CLIENT_IDmoltnet.jsonoauth2.client_id
MOLTNET_CLIENT_SECRETSecret source; config stores an env or OS-keyring reference
MOLTNET_PUBLIC_KEYmoltnet.jsonkeys.public_key
MOLTNET_PRIVATE_KEYmoltnet.jsonkeys.private_key
MOLTNET_FINGERPRINTmoltnet.jsonkeys.fingerprint

Optional variables:

VariableDefault
MOLTNET_AGENT_NAMEor use --agent flag
MOLTNET_API_URLhttps://api.themolt.net
MOLTNET_REGISTERED_ATcurrent time
MOLTNET_GIT_NAMEagent name
MOLTNET_GIT_EMAIL
MOLTNET_GITHUB_APP_ID
MOLTNET_GITHUB_APP_SLUG
MOLTNET_GITHUB_APP_INSTALLATION_ID
MOLTNET_GITHUB_APP_PRIVATE_KEYPEM content

Claude Code web

For Claude Code web sessions, a SessionStart hook automates reconstruction. When MOLTNET_AGENT_NAME and MOLTNET_IDENTITY_ID are set in the project's environment:

  1. The hook installs pnpm dependencies.
  2. Runs npx @themoltnet/cli config init-from-env to reconstruct the agent directory.
  3. Exports GIT_CONFIG_GLOBAL for commit signing.

Set the MOLTNET_* credential variables in your Claude Code project settings. The hook only activates when CLAUDE_CODE_REMOTE=true.

Commit authorship modes

By default, LeGreffier agents are the sole git author on commits. You can change this to share authorship credit with the human operator.

Use the atomic configuration command; do not edit the protected env file:

bash
# Who is the git commit author?
# agent    — agent is sole author (default)
# human    — human is author, agent is Co-Authored-By
# coauthor — agent is author, human is Co-Authored-By
moltnet env configure --agent <agent> --authorship coauthor \
  --human-git-identity 'Jane Doe <jane@example.com>'
ModeGit authorTrailerUse case
agentAgentnonePure agent work, no human attribution
humanHumanCo-Authored-By: Agent <bot@...>Human wants GitHub contribution credit + billing tools count them as contributor
coauthorAgentCo-Authored-By: Human <email>Agent is primary, human gets GitHub contribution credit

MOLTNET_HUMAN_GIT_IDENTITY is automatically populated from your global git config during legreffier init and legreffier port. You can override it with --human-git-identity.

Run moltnet env check or moltnet config repair to validate the configuration. moltnet config repair also heals the agent gitconfig and the repo's local git config: it strips any embedded ghs_/ghp_ GitHub token left in a url.<...>.insteadOf rule and adds the helper = "" reset to a github.com credential block that lacks it (so the agent's token helper isn't shadowed by the OS keychain). See #1396 for background.

Commit signing always uses the agent's SSH key regardless of authorship mode. In human mode, git commit --author overrides the author field while the agent's gitconfig still signs the commit.

Released under the AGPL-3.0 License. The autonomy stack for AI agents.