Providers
Reference for accounts: hmz.coganchor.providers. A provider is one named set of credentials for one coding agent CLI, kept apart from the CLI's own. An agent configured with a provider runs every turn with that provider's variables and reads and writes its credential files in the provider's directory. Two agents of one CLI can therefore run as two accounts at once.
Identity
| Aspect | Rule |
|---|---|
| Name | <cli>/<name>, e.g. claude/work. <cli> is the backend's name (any alias is accepted and normalised). |
<name> | [A-Za-z0-9][A-Za-z0-9._-]* |
| The machine's own account | <cli>/ with the empty name, providers.LOCAL == "". Exists for every backend; Humanize keeps no credentials for it, and turns under it are the CLI as already signed in. Nothing about it can be set. |
| Chosen with | -a ROLE=CLI@NAME/MODEL:EFFORT; AgentConfig.provider="NAME"; an agent's sheet in the TUI (TUI) |
| Which CLIs | every built-in backend, and every ACP CLI added on this machine (which has only the env way) |
| Input | ValueError |
|---|---|
| a backend no profile answers to | nosuch: unknown agent |
| a bad name | 'a/b' is not a valid account name: letters, digits, dot, dash and underscore, starting with a letter or a digit |
an agent whose provider names no account (raised the first time a turn needs it; a run given one in an -a spec is refused before it starts instead) | <agent id>: no <cli> provider called '<name>' |
Defined in src/hmz/coganchor/providers/store.py, specs/features/accounts.md.
Storage
The rest of Humanize's home is in Files.
$HUMANIZE_HOME/ default ~/.hmz
├── providers/<cli>/<name>/ one account
│ ├── provider.json what it is
│ ├── home/… credential files under the CLI's home
│ ├── user/… credential files under the user's home (~/…)
│ └── config/… credential files under $XDG_CONFIG_HOME
$TMPDIR/humanize-<uid>/models/<cli>/ model catalogues: a cache of this machine's
├── <name>.json what an account was last found to run
└── _local.json the same for the machine's own account| Rule | |
|---|---|
| Modes | Every directory Humanize creates on the way to providers/<cli>/<name>/ and to each credential's parent is 0700. Every file is written to a file of its own beside it (.<file>.<random>.new, mode 0600 from creation), fsynced and renamed into place. |
| Directory name | The backend's canonical name and the account's name. The directory, not the file, decides which backend and name an account has. |
| Unreadable entries | A directory whose provider.json is missing or not a JSON object is not listed. |
| Removal | remove(cli, name) deletes the whole directory, credentials included, and the account's model catalogue. |
provider.json
| Key | Type | Meaning |
|---|---|---|
cli | string | the backend, as named here |
name | string | the account's name |
way | string | the way it was made by; env if missing |
env | object of strings | variables a turn under it is given |
args | array of strings | arguments appended to the backend's command line |
made | string | UTC time made, YYYY-MM-DDTHH:MM:SSZ |
{
"cli": "claude",
"name": "work",
"way": "key",
"env": {"ANTHROPIC_API_KEY": "sk-ant-…"},
"args": [],
"made": "2026-09-30T05:49:08Z"
}A fallback key written by an older version is ignored, and dropped the next time the account is written. So is local/<cli>.json, which held only that.
Model catalogues
The account's model catalogue, kept on this machine (hmz.machine()) rather than in Humanize's home, at models/<cli>/<name>.json and at models/<cli>/_local.json for the machine's own account, whose name no account can take. Written by hmz.coganchor.models.ask when the account is made and when it is asked again (the Check again button of the TUI's model list). Read by every prompt. Older than 7 days (models.STALE) counts as stale; at start-up the TUI asks again, in the background, for each installed backend whose machine's-own catalogue is missing or stale.
{"asked": "2026-09-30T05:49:08Z", "models": [{"name": "claude-opus-5", "efforts": ["max", "high"], "swarms": false}]}Where the backend has an endpoint variable and the account sets it, the catalogue is the endpoint's GET {base}/v1/models ({base}/models when the base already ends in a version such as /v1), sent with the account's secret as both Authorization: Bearer and x-api-key and anthropic-version: 2023-06-01, 20 s timeout, 8 MiB limit, redirects followed only on the same host and never from https down to http. An endpoint that does not answer leaves an endpoint-sourced catalogue in place. Otherwise the CLI is asked (see Agents › Models).
Defined in src/hmz/coganchor/providers/store.py, src/hmz/coganchor/models.py.
The ways in
A way is one kind of account a backend offers. ways(cli) returns the backend's own ways in order, then every way of litellm's through a local gateway where the backend has them and the [litellm] extra is installed (Through LiteLLM), then env for every backend but dsh and litellm: a litellm turn is a call in this process, handed the account's credentials as parameters, so it has no environment of its own for variables to be set in.
hmz.coganchor.backends.Way:
| Field | Type | Meaning |
|---|---|---|
name | str | what the way is called; recorded as the provider's way |
about | str | one line describing it |
argv | tuple[str, ...] | the backend's own command to run, under the provider's paths, with the terminal handed over; () for a way that is only answers. {VARIABLE} is filled from the answers. |
asks | tuple[Asked, ...] | questions, in order |
sets | tuple[(str, str), ...] | variables always set, whatever the answers |
args | tuple[str, ...] | arguments appended to every turn's command line, {VARIABLE} filled |
stdin | str | the variable whose answer is written to argv's stdin |
hmz.coganchor.backends.Asked:
| Field | Type | Default | Meaning |
|---|---|---|---|
env | str | required | the variable the answer becomes |
about | str | required | the question |
secret | bool | False | drawn as bullets, never shown back |
keep | bool | True | kept in env; False for an answer only handed to argv |
fixed | str | "" | the answer when none is given |
Ways by backend
| Backend | Way | Runs | Asks (secret •, not kept ◦, default in parentheses) | Sets |
|---|---|---|---|---|
claude | login | claude auth login | — | |
console | claude auth login --console | — | ||
token | — | CLAUDE_CODE_OAUTH_TOKEN • | ||
key | — | ANTHROPIC_API_KEY • | ||
wif | — | ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_IDENTITY_TOKEN_FILE (a file path) | ||
anthropic-gateway | — | ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN • | ||
bedrock-gateway | — | ANTHROPIC_BEDROCK_BASE_URL, ANTHROPIC_AUTH_TOKEN • | CLAUDE_CODE_USE_BEDROCK=1, CLAUDE_CODE_SKIP_BEDROCK_AUTH=1 | |
vertex-gateway | — | ANTHROPIC_VERTEX_BASE_URL, ANTHROPIC_AUTH_TOKEN •, ANTHROPIC_VERTEX_PROJECT_ID, CLOUD_ML_REGION (us-east5) | CLAUDE_CODE_USE_VERTEX=1, CLAUDE_CODE_SKIP_VERTEX_AUTH=1 | |
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | CLAUDE_CODE_USE_BEDROCK=1 | |
bedrock-key | — | AWS_BEARER_TOKEN_BEDROCK •, AWS_REGION (us-east-1) | CLAUDE_CODE_USE_BEDROCK=1 | |
mantle | — | AWS_PROFILE, AWS_REGION (us-east-1) | CLAUDE_CODE_USE_MANTLE=1 | |
vertex | — | ANTHROPIC_VERTEX_PROJECT_ID, CLOUD_ML_REGION (us-east5) | CLAUDE_CODE_USE_VERTEX=1 | |
foundry | — | ANTHROPIC_FOUNDRY_RESOURCE, ANTHROPIC_FOUNDRY_API_KEY • | CLAUDE_CODE_USE_FOUNDRY=1 | |
aws | — | ANTHROPIC_AWS_WORKSPACE_ID, AWS_REGION (us-east-1), ANTHROPIC_AWS_API_KEY • | CLAUDE_CODE_USE_ANTHROPIC_AWS=1 | |
google-cloud | — | ANTHROPIC_GOOGLE_CLOUD_PROJECT, ANTHROPIC_GOOGLE_CLOUD_LOCATION (global), ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID | CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD=1 | |
agy | login | agy (interactive) | — | |
key | — | GEMINI_API_KEY • | ||
gemini-gateway | — | AGY_LLM_GATEWAY_URL, AGY_LLM_GATEWAY_API_KEY • | AGY_LLM_GATEWAY_WIRE_PROTOCOL=genai | |
openai-gateway | — | AGY_LLM_GATEWAY_URL, AGY_LLM_GATEWAY_API_KEY •, AGY_LLM_GATEWAY_MODELS (comma-separated) | AGY_LLM_GATEWAY_WIRE_PROTOCOL=openai | |
adc | — | GOOGLE_APPLICATION_CREDENTIALS (a file path) | AGY_ADC_AUTH=1 | |
codex | login | codex login | — | |
device | codex login --device-auth | — | ||
key | codex login --with-api-key, key on stdin | OPENAI_API_KEY • ◦ | ||
token | codex login --with-access-token, token on stdin | CODEX_ACCESS_TOKEN • ◦ | ||
workload | — | OPENAI_FEDERATION_RULE_ID, OPENAI_IDENTITY_TOKEN_FILE (a file path) | ||
openai-gateway | — | CODEX_PROVIDER_URL, CODEX_PROVIDER_KEY • | appends -c arguments (below) | |
azure | — | AZURE_OPENAI_BASE_URL (https://<resource>.openai.azure.com/openai), AZURE_OPENAI_API_KEY •, AZURE_OPENAI_API_VERSION (2025-04-01-preview) | appends -c arguments (below) | |
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | appends -c model_provider=amazon-bedrock -c model_providers.amazon-bedrock.aws.profile=<AWS_PROFILE> -c model_providers.amazon-bedrock.aws.region=<AWS_REGION> | |
bedrock-key | — | AWS_BEARER_TOKEN_BEDROCK •, AWS_REGION (us-east-1) | appends -c model_provider=amazon-bedrock -c model_providers.amazon-bedrock.aws.region=<AWS_REGION> | |
ollama | — | CODEX_OSS_BASE_URL (http://localhost:11434/v1) | appends -c model_provider=ollama | |
lmstudio | — | CODEX_OSS_BASE_URL (http://localhost:1234/v1) | appends -c model_provider=lmstudio | |
cursor-agent | login | cursor-agent login | — | |
key | — | CURSOR_API_KEY • | ||
cursor-gateway | — | CURSOR_API_ENDPOINT (an endpoint speaking Cursor's own protocol), CURSOR_API_KEY • | ||
dsh | key | — | DEEPSEEK_API_KEY • | |
openai-gateway | — | DEEPSEEK_BASE_URL (ending in /v1), DEEPSEEK_API_KEY •, DSH_GATEWAY_API (openai-completions; or openai-responses) | ||
anthropic-gateway | — | DEEPSEEK_BASE_URL (without /v1), DEEPSEEK_API_KEY • | ||
gemini-gateway | — | DEEPSEEK_BASE_URL (ending in /v1beta), DEEPSEEK_API_KEY • | ||
litellm | openai-key, anthropic-key, gemini-key, xai-key, openrouter-key, deepseek-key, groq-key, mistral-key | — | the vendor's own variable •: OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY; passed as api_key | |
openai-gateway | — | LITELLM_GATEWAY_URL, LITELLM_GATEWAY_KEY •; passed as api_base, api_key | LITELLM_GATEWAY_API=openai | |
anthropic-gateway | — | LITELLM_GATEWAY_URL, LITELLM_GATEWAY_KEY •; passed as api_base, api_key | LITELLM_GATEWAY_API=anthropic | |
bedrock | — | AWS_PROFILE, AWS_REGION_NAME (us-east-1); passed as aws_profile_name, aws_region_name | ||
vertex | — | VERTEXAI_PROJECT, VERTEXAI_LOCATION (us-central1); Application Default Credentials | ||
azure | — | AZURE_API_BASE, AZURE_API_KEY •, AZURE_API_VERSION (2024-10-21); passed as api_base, api_key, api_version | ||
grok | login | grok login | — | |
device | grok login --device-auth | — | ||
oidc | grok login | GROK_OIDC_ISSUER, GROK_OIDC_CLIENT_ID | ||
provider-command | grok login | GROK_AUTH_PROVIDER_COMMAND (prints a token) | ||
key | — | XAI_API_KEY • | ||
openai-gateway | — | GROK_XAI_API_BASE_URL (models listed at /models), XAI_API_KEY •, GROK_GATEWAY_API_BACKEND (responses; responses or chat_completions) | ||
anthropic-gateway | — | GROK_XAI_API_BASE_URL (models listed at /models), XAI_API_KEY • | GROK_GATEWAY_API_BACKEND=messages | |
kimi | login | kimi login --region {KIMI_REGION} | KIMI_REGION ◦ (global; global or mainland-cn) | |
kimi-key | — | KIMI_MODEL_API_KEY •, KIMI_MODEL_BASE_URL (https://api.moonshot.ai/v1; Kimi for Coding is https://api.kimi.com/coding/v1), KIMI_MODEL_NAME | KIMI_MODEL_PROVIDER_TYPE=kimi | |
openai-gateway | — | KIMI_MODEL_BASE_URL, KIMI_MODEL_API_KEY •, KIMI_MODEL_NAME, KIMI_MODEL_PROVIDER_TYPE (openai, Chat Completions; or openai_responses, Responses) | ||
anthropic-gateway | — | KIMI_MODEL_BASE_URL, KIMI_MODEL_API_KEY •, KIMI_MODEL_NAME | KIMI_MODEL_PROVIDER_TYPE=anthropic | |
gemini-gateway | — | KIMI_MODEL_BASE_URL (the root /v1beta goes under), KIMI_MODEL_API_KEY •, KIMI_MODEL_NAME | KIMI_MODEL_PROVIDER_TYPE=google-genai | |
mcode | login | mcode login --region {MCODE_REGION} | MCODE_REGION ◦ (global; global or cn) | |
key | mcode provider set-minimax-key | MCODE_PROVIDER_API_KEY •, MAVIS_REGION (en; en, for platform.minimax.io, or cn, for platform.minimaxi.com) | ||
openai-gateway | mcode provider add --name gateway --base-url {MCODE_GATEWAY_URL} --api-format {MCODE_GATEWAY_FORMAT} --model {MCODE_GATEWAY_MODEL} --api-key-env MCODE_PROVIDER_API_KEY --use | MCODE_GATEWAY_URL, MCODE_PROVIDER_API_KEY •, MCODE_GATEWAY_MODEL ◦, MCODE_GATEWAY_FORMAT ◦ (openai-completions; openai-completions or openai-responses) | ||
anthropic-gateway | mcode provider add --name gateway --base-url {MCODE_GATEWAY_URL} --api-format anthropic-messages --model {MCODE_GATEWAY_MODEL} --api-key-env MCODE_PROVIDER_API_KEY --use | MCODE_GATEWAY_URL, MCODE_PROVIDER_API_KEY •, MCODE_GATEWAY_MODEL ◦ | ||
mimo | login | mimo auth login | — | |
key | — | XIAOMI_API_KEY • | ||
anthropic-key | — | ANTHROPIC_API_KEY • | ||
openai-key | — | OPENAI_API_KEY • | ||
gemini-key | — | GOOGLE_GENERATIVE_AI_API_KEY • | ||
xai-key | — | XAI_API_KEY • | ||
openrouter-key | — | OPENROUTER_API_KEY • | ||
deepseek-key | — | DEEPSEEK_API_KEY • | ||
openai-gateway | — | MIMO_GATEWAY_URL, MIMO_GATEWAY_KEY •, MIMO_GATEWAY_MODEL, MIMO_GATEWAY_API (@ai-sdk/openai-compatible for Chat Completions; @ai-sdk/openai for Responses) | MIMOCODE_CONFIG_CONTENT (below) | |
anthropic-gateway | — | MIMO_GATEWAY_URL, MIMO_GATEWAY_KEY •, MIMO_GATEWAY_MODEL | MIMO_GATEWAY_API=@ai-sdk/anthropic, MIMOCODE_CONFIG_CONTENT | |
gemini-gateway | — | MIMO_GATEWAY_URL, MIMO_GATEWAY_KEY •, MIMO_GATEWAY_MODEL | MIMO_GATEWAY_API=@ai-sdk/google, MIMOCODE_CONFIG_CONTENT | |
vertex | — | GOOGLE_CLOUD_PROJECT, GOOGLE_VERTEX_LOCATION (us-central1); signed in by application default credentials | ||
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | ||
azure | — | AZURE_RESOURCE_NAME, AZURE_API_KEY • | ||
omp | anthropic-key | — | ANTHROPIC_API_KEY • | |
anthropic-token | — | ANTHROPIC_OAUTH_TOKEN • | ||
openai-key | — | OPENAI_API_KEY • | ||
gemini-key | — | GEMINI_API_KEY • | ||
xai-key | — | XAI_API_KEY • | ||
openrouter-key | — | OPENROUTER_API_KEY • | ||
deepseek-key | — | DEEPSEEK_API_KEY • | ||
groq-key | — | GROQ_API_KEY • | ||
mistral-key | — | MISTRAL_API_KEY • | ||
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | ||
vertex | — | GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION (us-central1) | ||
opencode | login | opencode auth login | — | |
wellknown | opencode auth login {OPENCODE_WELLKNOWN} | OPENCODE_WELLKNOWN ◦ (URL answering at /.well-known/opencode) | ||
zen | — | OPENCODE_API_KEY • | ||
anthropic-key | — | ANTHROPIC_API_KEY • | ||
openai-key | — | OPENAI_API_KEY • | ||
gemini-key | — | GOOGLE_GENERATIVE_AI_API_KEY • | ||
xai-key | — | XAI_API_KEY • | ||
openrouter-key | — | OPENROUTER_API_KEY • | ||
deepseek-key | — | DEEPSEEK_API_KEY • | ||
mistral-key | — | MISTRAL_API_KEY • | ||
openai-gateway | — | OPENCODE_GATEWAY_URL, OPENCODE_GATEWAY_KEY •, OPENCODE_GATEWAY_MODEL, OPENCODE_GATEWAY_NPM (@ai-sdk/openai-compatible for Chat Completions; @ai-sdk/openai for Responses) | OPENCODE_CONFIG_CONTENT (below) | |
anthropic-gateway | — | OPENCODE_GATEWAY_URL, OPENCODE_GATEWAY_KEY •, OPENCODE_GATEWAY_MODEL | OPENCODE_CONFIG_CONTENT (below) | |
gemini-gateway | — | OPENCODE_GATEWAY_URL, OPENCODE_GATEWAY_KEY •, OPENCODE_GATEWAY_MODEL | OPENCODE_CONFIG_CONTENT (below) | |
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | ||
vertex | — | GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION (global); application default credentials | ||
azure | — | AZURE_RESOURCE_NAME, AZURE_API_KEY • | ||
pi | login | pi (interactive: /login, then /exit) | — | |
anthropic-key | — | ANTHROPIC_API_KEY • | ||
anthropic-token | — | ANTHROPIC_OAUTH_TOKEN • | ||
openai-key | — | OPENAI_API_KEY • | ||
gemini-key | — | GEMINI_API_KEY • | ||
xai-key | — | XAI_API_KEY • | ||
openrouter-key | — | OPENROUTER_API_KEY • | ||
deepseek-key | — | DEEPSEEK_API_KEY • | ||
groq-key | — | GROQ_API_KEY • | ||
mistral-key | — | MISTRAL_API_KEY • | ||
openai-gateway | — | PI_GATEWAY_URL, PI_GATEWAY_KEY •, PI_GATEWAY_MODEL, PI_GATEWAY_API (openai-completions; openai-completions or openai-responses) | a models.json provider (below) | |
anthropic-gateway | — | PI_GATEWAY_URL, PI_GATEWAY_KEY •, PI_GATEWAY_MODEL | PI_GATEWAY_API=anthropic-messages | |
gemini-gateway | — | PI_GATEWAY_URL, PI_GATEWAY_KEY •, PI_GATEWAY_MODEL | PI_GATEWAY_API=google-generative-ai | |
bedrock | — | AWS_PROFILE, AWS_REGION (us-east-1) | ||
vertex | — | GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION (us-central1) | ||
azure | — | AZURE_OPENAI_BASE_URL, AZURE_OPENAI_API_KEY • | ||
qwen | coding-plan | — | OPENAI_BASE_URL (https://coding.dashscope.aliyuncs.com/v1), OPENAI_API_KEY • | appends --auth-type openai |
token-plan | — | OPENAI_BASE_URL (https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1), OPENAI_API_KEY • | appends --auth-type openai | |
gemini-key | — | GEMINI_API_KEY • | appends --auth-type gemini | |
openai-gateway | — | OPENAI_BASE_URL (https://dashscope.aliyuncs.com/compatible-mode/v1), OPENAI_API_KEY •, QWEN_DEFAULT_AUTH_TYPE (openai; openai or openai-responses) | appends --auth-type {QWEN_DEFAULT_AUTH_TYPE} | |
anthropic-gateway | — | ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY • | appends --auth-type anthropic | |
gemini-gateway | — | GOOGLE_GEMINI_BASE_URL, GEMINI_API_KEY • | appends --auth-type gemini | |
vertex | — | GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION (global); Application Default Credentials | appends --auth-type vertex-ai | |
vertex-key | — | GOOGLE_API_KEY • (Vertex AI express mode) | appends --auth-type vertex-ai | |
every backend but dsh and litellm; ACP CLIs | env | — | NAME=VALUE lines |
claude'sawsandgoogle-cloudare Claude Platform on AWS and on Google Cloud (Anthropic's API, billed through that cloud);mantleis Amazon Bedrock's Mantle endpoint, whose model ids areanthropic.claude-….google-cloudandvertexsign with Google Application Default Credentials,bedrockandmantlewith the AWS profile.- A model on an
mcodeopenai-gatewayoranthropic-gatewayaccount is namedcustom_provider:gateway/<id>. - An
mcodekeyaccount withMAVIS_REGIONother thanentakes its turns atapi.minimaxi.com; aloginaccount's region is the one it signed in to. - A model on a
mimogateway account is named by the endpoint's own id, or ashumanize/<id>; either way a turn asksmimoforhumanize/<id>. - A model on a
pikey or cloud account is namedprovider/id(anthropic/…,openai/…,google/…,amazon-bedrock/…,google-vertex/…,azure-openai-responses/…); on apigateway account it is the gateway's own id. - A model on an
ompaccount is namedprovider/id, asomp models --jsonlists it (anthropic/…,deepseek/…). omphas nologin: omp keeps its sign-ins inagent.db, beside its settings, which an account does not copy. Anompagent with no account runs as this machine's omp sign-in.mcode'sconfig.yamlis a credential file, so a provider of it holds settings of its own.cursor-agent'scli-config.jsonis a credential file and also its settings.qwenhas nologin: Qwen OAuth was discontinued on 2026-04-15 andqwen0.24.7 refuses--auth-type qwen-oauth. A plan's key goes in asOPENAI_API_KEY; theBAILIAN_*names/authkeeps it under are read only through asettings.jsonmodelProvidersentry.agy's two gateway ways are its enterprise LLM gateway. The key goes asAuthorization: Bearer.gemini-gatewaystreams<url>/v1beta/models/<model>:streamGenerateContentand its models are agy's own Gemini models.openai-gatewaystreams<url>/v1/chat/completions, andAGY_LLM_GATEWAY_MODELSis every model it may run:agy modelslists exactly those, at no effort, since agy refuses--effortfor them.agy'skeyway needs"modelProvider": "gemini"in~/.gemini/antigravity-cli/settings.json(agy 1.1.13 and later). That file is shared by every agy account, and agy refuses to start while the setting is there withoutGEMINI_API_KEY, so Humanize does not write it.
The env way
env_of(text)readsNAME=VALUElines: surrounding whitespace is stripped, blank lines and lines starting#are skipped, a value may contain=. A non-empty line without=(or with an empty name) raisesValueError: '<line>' is not NAME=VALUE.- Every variable given is kept, whatever its name.
Making, signing in, rewriting
| Call | Behaviour |
|---|---|
login.make(cli, name, way, answers=None) | Fills unanswered questions from fixed; keeps answers whose Asked.keep is true (all of them for env) and are non-empty; adds sets; fills args; writes the account with add. |
login.asked(way, given) | The variables still to be answered: neither given nor fixed. |
login.sign_in(provider, way, answers=None) | Runs argv (filled) under the provider's credential paths (hmz internal cred), environment os.environ plus the provider's env, writing answers[way.stdin] + "\n" to stdin where stdin is set. Returns the exit status: 0 for a way with no argv, 127 for a CLI not installed. |
add(cli, name, way="env", env=None, args=()) | Writes provider.json, replacing way, env, args and made; leaves credential files in place; makes every credential parent directory. |
ready(provider) | Makes every credential parent directory (0700). |
remove(cli, name) -> bool | Deletes the account directory. |
The machine's own account cannot be made, signed in or removed.
Defined in src/hmz/coganchor/backends.py (Way, Asked, PROFILES), src/hmz/coganchor/providers/login.py.
Credential files
Profile.creds lists the files a backend's login leaves. An entry is under the backend's home unless it starts ~/ (the user's home) or config/ ($XDG_CONFIG_HOME, else ~/.config). A directory entry covers everything inside it. In the provider's directory the three roots are home/, user/ and config/.
| Backend | Home | Credential files |
|---|---|---|
agy | ~/.gemini/antigravity-cli (only HOME moves it) | antigravity-oauth-token |
claude | $CLAUDE_CONFIG_DIR, else ~/.claude | .credentials.json, .claude.json, ~/.claude.json, config/anthropic/ |
codex | $CODEX_HOME, else ~/.codex | auth.json |
cursor-agent | $CURSOR_CONFIG_DIR, else ~/.cursor | cli-config.json, config/cursor/auth.json, ~/.cursor/auth.json |
dsh | $DSH_HOME, else ~/.dsh | none |
litellm | ~/.cache/humanize/litellm | none |
grok | $GROK_HOME, else ~/.grok | auth.json, mcp_credentials.json |
kimi | $KIMI_CODE_HOME, else ~/.kimi-code | credentials/, oauth/ |
mcode | $MINIMAX_DATA_DIR, else ~/.minimax | config.yaml, auth/ |
mimo | $XDG_DATA_HOME/mimocode, else ~/.local/share/mimocode | auth.json, mcp-auth.json |
omp | $PI_CODING_AGENT_DIR, else ~/.omp/agent | none: its sign-ins are in its agent.db, which is not copied |
opencode | $XDG_DATA_HOME/opencode, else ~/.local/share/opencode | auth.json, mcp-auth.json |
pi | $PI_CODING_AGENT_DIR, else ~/.pi/agent | auth.json, auth.json.lock |
qwen | $QWEN_HOME, else ~/.qwen | none |
| an ACP CLI | none known | none |
Provider.swaps() is one (path the CLI names, path in the provider's directory) pair per entry, plus the same pair with symlinks resolved where the resolved path differs. It is empty for the machine's own account and for a backend with no credential files.
Not covered: a credential that is not a file (a macOS keychain); an opencode or mimocode console account, which lives in the same SQLite database as its sessions; any path not listed.
How a credential path is answered
A turn under a provider whose swaps() is non-empty is spawned as:
python -Pm hmz internal cred --map=FROM=TO [--map=…] [--keep=FROM=TO …] -- CLI ARGS…hmz internal cred runs the CLI under a seccomp-filtered ptrace supervisor that stops only path-naming syscalls: the same set an anchored supervisor stops, less execve, execveat and connect (Remote execution › Interception). A path is matched after ., .. and doubled / are resolved and /proc/<pid>/fd/<n>, cwd, exe and root links are followed, as the file itself, as anything inside a directory entry, or as the same name with a suffix (.credentials.json.tmp, which is how a token is rotated by rename).
| Syscall | Answered with |
|---|---|
stat, lstat, newfstatat, statx, access, faccessat, faccessat2, readlink, readlinkat, and open/openat/openat2 without O_WRONLY, O_RDWR, O_CREAT, O_TRUNC or O_APPEND | a copy in /dev/shm/hmz-<pid>-<random>/ (directory 0700, files 0600), made on first read and re-checked against the provider's file once a second |
everything else (create, write, truncate, rename, link, unlink, mknod, chmod, chown, utime, setxattr/removexattr, open for writing) | the provider's own file; the copy is dropped |
statfs, getxattr/listxattr (and their l forms), name_to_handle_at, open_tree, inotify_add_watch, fanotify_mark | the provider's own file; the copy is kept |
chdir, and reads of directories, lock files, files over 1 MiB, or anything when /dev/shm is unusable | the provider's own path |
- A path that cannot be answered fails with
EIOrather than falling through to the real one; anopenat2withRESOLVE_BENEATHorRESOLVE_IN_ROOTnaming an answered path is refused. - The CLI is a child of
hmz internal cred, which exits with the CLI's status as soon as the CLI exits; killing it kills the CLI. The supervisor runs outside that process tree, in a session of its own, and traces the CLI withPTRACE_SEIZE(the CLI names it withPR_SET_PTRACER), so ending the turn's tree or process group does not reach it. - When the CLI exits or is killed, everything it started is killed, except a process that left it while it ran: one whose parent, or a parent's parent below the CLI, had already exited, as with
setsid nohup … &or(cmd &). Such a process is still supervised, so its credential paths are still answered, and it is left running after the turn. The supervisor exits when the last of them does. A process that only started a session or a process group of its own (setsid cmd &from a shell that is still running) has not left, and is killed. Inside a fence that cuts the network, a process that left loses the fence's proxy when the turn ends, and is killed with a turn that is cut off. - The copies are removed when the supervisor exits; a killed supervisor's copies are removed by the next redirected run on the machine.
- The filter lets other architectures' syscalls through: a 32-bit process below the CLI is not intercepted.
- Requires Linux on x86-64 or aarch64 with ptrace permitted. A backend with no credential files (
dsh,omp,qwen, ACP CLIs), and a provider whose credentials are only variables on such a backend, needs no supervisor for credentials. - An anchored turn is not wrapped: a process has one tracer, so the anchor is given the same pairs as
redirectsand its own supervisor answers them (Remote execution › Where the account lives).
Defined in src/hmz/coganchor/providers/redirect.py, _trace.py, _staging.py, src/hmz/coganchor/pathcalls.py.
A sign-in that refreshes itself
Some logins keep a refresh token in their credential file, and each refresh writes a new one and spends the old one: Codex signed in with ChatGPT (auth.json), Claude Code with a subscription (.credentials.json), and the OAuth logins of cursor-agent, kimi, opencode, mimo and pi. A spent refresh token presented again is read by the vendor as a stolen one, and the whole sign-in is revoked, every copy of it included. API keys, gateway tokens and CLAUDE_CODE_OAUTH_TOKEN do not rotate.
So such a file is never copied somewhere it could refresh on its own:
| Where the turn runs | What it refreshes |
|---|---|
| This machine, or a supervised anchor | the account's own file: a write is always answered with it, never with a copy |
A native anchor (the runtime's harness env, the default) | a copy on the target, sent only while no other turn is using the file, and no other turn may until it is back; when the turn ends, the refreshed copy is written back over the account's file, unless that file changed meanwhile |
The machine's own sign-in (no @account) | never sent anywhere: a CLI on another machine uses that machine's own sign-in |
A file holding refresh_token, refreshToken or a "refresh": key counts as one. Each turn holds the directory it is in with flock for its length: shared by turns using it in place, exclusive for a native turn sending a copy. Neither waits for the other, since the holder may be a session open until the run ends; the turn that finds it held the other way is refused with … this account signs in with a token that refreshes itself, and another turn is using it … (native) or … and a copy of it is out on another machine for a turn there … (here), which is the contended fault, tried three times a second apart. Run every role of that account with its harness here (the runtime's harness local), give the native one an account signed in with a key, or sign the CLI in on the host and use no @account there. A filesystem that cannot lock a directory (some NFS) logs cannot hold … for this turn and runs the turn unheld.
If a login was revoked anyway (refresh token was revoked, (refused: that account needs signing in again)), sign it in again: codex login or claude auth login for this machine's own, or sign in again on the account under /settings accounts. Nothing else restores it.
Defined in src/hmz/coganchor/anchor.py (_lending, _brought_back), src/hmz/coganchor/_lending.py.
Where sessions are kept
The same supervisor keeps the sessions of each turn a run drives out of the CLI's home, under whatever account it runs, the machine's own included. Each Profile.sessions entry under the CLI's home is passed as --keep=FROM=TO, TO being the same relative path under the agent's session directory:
| Agent | Session directory |
|---|---|
| driven by a flow run | the run's epic: <epic>/sessions/<cli>/… (Tracing) |
| driven by hand | none: sessions stay in the CLI's own home |
A kept path is always answered with the file itself (never a copy), its directories are made as the CLI writes into them, and an entry may be a glob of one path component.
| CLI | Kept, relative to its home |
|---|---|
claude | projects, sessions, file-history, session-env, tasks, todos, plans |
agy | conversations, brain, annotations, implicit, presence, conversation_summaries.db*, jetbox_summaries_proto.pb, cache/last_conversations.json |
codex | sessions, archived_sessions, session_index.jsonl, state_*.sqlite*, thread_history_*.sqlite*, goals_*.sqlite*, queue_*.sqlite*, memories_*.sqlite*, thread-writer-locks, shell_snapshots |
cursor-agent | chats, projects/*/agent-transcripts |
dsh | sessions, named to the SDK as its session root (Profile.told) rather than supervised |
litellm | sessions, written by Humanize's own driver (Profile.told) |
grok | sessions, active_sessions.* |
kimi | sessions, session_index.jsonl, workspaces.json, server/events, search-index, file-history |
mcode | v2/sqlite, v2/sessions, background-tasks |
mimo | mimocode.db*, storage |
omp | sessions |
opencode | opencode.db*, storage |
pi | sessions |
qwen | projects, tmp, file-history |
Where this machine cannot supervise a turn (not Linux on x86-64/aarch64, or starting a traced no-op program fails; the answer is cached, and a failure is asked again after a minute), a CLI started in a virtual home whose kept paths are each a name directly under its home (claude, codex, qwen, grok, pi, omp) still keeps them in the run. In that home, each kept name is a symbolic link to <epic>/sessions/<cli>/<name> in place of the user's own entry. A glob such as Codex's state_*.sqlite* links every matching name in either directory, whether or not the run has that file yet. A database the CLI creates there under a new name is moved into <epic>/sessions/<cli>/ when the home is removed.
Sessions stay where the CLI keeps them, and nothing is supervised for them, when any of these holds:
HUMANIZE_SESSIONSisoff,0orno(credentials are still supervised);- this machine cannot supervise a turn, and the CLI is not one linked as above (
agy,kimi,cursor-agent,mcode, whose kept paths lie below a directory of their home, andopencode,mimo, which start in no virtual home); - the turn is native on a target, or its harness runs on another machine.
Sessions on another machine
A turn that is native on a target, or whose harness runs on another machine, keeps its session in that CLI's home over there. For an agent a run drives, every file the backend logs the session to (Profile.logs, e.g. Claude's projects/*/<id>.jsonl and its sub-agents') is copied into <epic>/sessions/<cli>/…, laid out the same way, from the moment the CLI names the session:
- every 2 s (
hmz.coganchor.tailing.EVERY), whole lines of each.jsonllog only, at most 8 MiB of a log per read, on a background thread; - whole as the session closes, which is before the run closes its environments (so before a container is removed), including a log written in place (agy's
conversations/<id>.db), which is copied only then. A session closed under a running turn (a stop), or let go of without a close, gets that last copy on a background thread instead; - down the road the turn takes (
ssh,docker exec,container exec,srun): one shortshcommand lists the files and their lengths, one more reads what was appended. Nothing is installed or held open there. Atcp://orpeer://target, on which no command is started, gets no copy.
The CLI's home over there is the one the turn had: the value of its home variable on this machine where that crosses to a native turn, else that machine's own, else its default under that machine's $HOME. A native turn whose provider projects the CLI's home (e.g. CLAUDE_CONFIG_DIR for a Claude subscription) keeps its sessions in the turn's private directory, whose name the target picks. While the turn runs it keeps where that landed in a file of its own in a private directory on this machine (AnchorConfig.tells, --tell), and the copy follows the session there every 2 s as above. As the turn ends, just before that directory is removed, it brings each directory the CLI logs sessions under (e.g. Claude's projects; never its credentials, nor an index every session shares) back into the epic as one tar (AnchorConfig.brings, --bring), spending at most 30 s on it. A failed copy is logged once at WARNING and retried; it never fails or slows a turn. agent.kept(), the epic's opened line, logs, traces, export and the running tally all read the copy. HUMANIZE_SESSIONS=off copies nothing.
MiniMax Code needs its sessions kept to run fenced: see Agents › MiniMax Code.
Defined in src/hmz/coganchor/backends.py (Profile.sessions, Profile.kept), src/hmz/coganchor/agents/base.py (KEEPING, keeps, _keeping, _tailing), src/hmz/coganchor/tailing.py (Tail, road), src/hmz/coganchor/providers/redirect.py (supervises).
A turn under a provider
| Effect | Rule |
|---|---|
| Added | provider.env, on top of the inherited environment (agent.environment()). |
| Appended | provider.args, after the CLI's own arguments. Only codex's openai-gateway, azure, bedrock, bedrock-key, ollama and lmstudio ways and every qwen way (its --auth-type) have any. |
| Removed | agent.hushed(): every variable the backend would read an account from (below), except those provider.env sets. |
| Redirected | provider.swaps(), as above. |
An agent with no provider (or the machine's own account) gets none of these: its environment is left exactly as found. All four apply whichever way the account was made.
Variables taken away
Profile.accounts() is every variable a way of the backend asks for or sets, plus Profile.ambient. Profile.hushes() adds every other name the same credential goes by, from backends.ALIKE:
| One credential | Every name |
|---|---|
| Anthropic subscription token | CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_OAUTH_TOKEN |
| Gemini key | GEMINI_API_KEY, GOOGLE_API_KEY |
| Moonshot key | MOONSHOT_API_KEY, KIMI_API_KEY |
| OpenAI base URL | OPENAI_BASE_URL, OPENAI_API_BASE |
| xAI key | XAI_API_KEY, GROK_CODE_XAI_API_KEY |
ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, OPENAI_API_KEY | one name each |
| Backend | hushes() |
|---|---|
agy | AGY_ADC_AUTH, AGY_LLM_GATEWAY_API_KEY, AGY_LLM_GATEWAY_CA_CERT, AGY_LLM_GATEWAY_HEADERS, AGY_LLM_GATEWAY_MODELS, AGY_LLM_GATEWAY_PROXY_URL, AGY_LLM_GATEWAY_URL, AGY_LLM_GATEWAY_WIRE_PROTOCOL, CLOUD_CODE_URL, GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_GEMINI_BASE_URL |
claude | ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_AWS_API_KEY, ANTHROPIC_AWS_BASE_URL, ANTHROPIC_AWS_WORKSPACE_ID, ANTHROPIC_BASE_URL, ANTHROPIC_BEDROCK_BASE_URL, ANTHROPIC_BEDROCK_MANTLE_BASE_URL, ANTHROPIC_CONFIG_DIR, ANTHROPIC_CUSTOM_HEADERS, ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_FOUNDRY_AUTH_TOKEN, ANTHROPIC_FOUNDRY_BASE_URL, ANTHROPIC_FOUNDRY_RESOURCE, ANTHROPIC_GOOGLE_CLOUD_BASE_URL, ANTHROPIC_GOOGLE_CLOUD_LOCATION, ANTHROPIC_GOOGLE_CLOUD_PROJECT, ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID, ANTHROPIC_IDENTITY_TOKEN, ANTHROPIC_IDENTITY_TOKEN_FILE, ANTHROPIC_MODEL, ANTHROPIC_OAUTH_TOKEN, ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_PROFILE, ANTHROPIC_SCOPE, ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_VERTEX_BASE_URL, ANTHROPIC_VERTEX_PROJECT_ID, ANTHROPIC_WORKSPACE_ID, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR, CLAUDE_CODE_OAUTH_REFRESH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR, CLAUDE_CODE_SKIP_ANTHROPIC_AWS_AUTH, CLAUDE_CODE_SKIP_ANTHROPIC_GOOGLE_CLOUD_AUTH, CLAUDE_CODE_SKIP_BEDROCK_AUTH, CLAUDE_CODE_SKIP_FOUNDRY_AUTH, CLAUDE_CODE_SKIP_MANTLE_AUTH, CLAUDE_CODE_SKIP_VERTEX_AUTH, CLAUDE_CODE_USE_ANTHROPIC_AWS, CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD, CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_FOUNDRY, CLAUDE_CODE_USE_GATEWAY, CLAUDE_CODE_USE_MANTLE, CLAUDE_CODE_USE_VERTEX, CLOUD_ML_REGION |
codex | AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_BASE_URL, CODEX_ACCESS_TOKEN, CODEX_API_KEY, CODEX_AUTHAPI_BASE_URL, CODEX_OSS_BASE_URL, CODEX_PROVIDER_KEY, CODEX_PROVIDER_URL, OPENAI_API_BASE, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_FEDERATION_RULE_ID, OPENAI_IDENTITY_TOKEN_FILE |
cursor-agent | CURSOR_API_BASE_URL, CURSOR_API_ENDPOINT, CURSOR_API_KEY, CURSOR_API_URL, CURSOR_AUTH_TOKEN, CURSOR_LOCAL_AGENT_API_KEY |
dsh | DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_SEARCH_BASE_URL, DSH_GATEWAY_API |
litellm | ANTHROPIC_API_KEY, AWS_PROFILE, AWS_REGION_NAME, AZURE_API_BASE, AZURE_API_KEY, AZURE_API_VERSION, DEEPSEEK_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, GROK_CODE_XAI_API_KEY, GROQ_API_KEY, LITELLM_GATEWAY_API, LITELLM_GATEWAY_KEY, LITELLM_GATEWAY_URL, MISTRAL_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, VERTEXAI_LOCATION, VERTEXAI_PROJECT, XAI_API_KEY; nothing is taken out of this process's environment, a turn being a call in it: the account is passed on the call instead |
grok | GROK_AUTH, GROK_AUTH_PATH, GROK_AUTH_PROVIDER_COMMAND, GROK_AUTH_PROVIDER_LABEL, GROK_AUTH_TOKEN_TTL, GROK_CLI_CHAT_PROXY_BASE_URL, GROK_CODE_XAI_API_KEY, GROK_CONFIG, GROK_CONFIG_PATH, GROK_DEFAULT_MODEL, GROK_GATEWAY_API_BACKEND, GROK_MODELS_BASE_URL, GROK_MODELS_LIST_URL, GROK_OAUTH2_CLIENT_ID, GROK_OAUTH2_ISSUER, GROK_OIDC_AUDIENCE, GROK_OIDC_CLIENT_ID, GROK_OIDC_ISSUER, GROK_OIDC_SCOPES, GROK_XAI_API_BASE_URL, XAI_API_KEY |
kimi | KIMI_API_KEY, KIMI_BASE_URL, KIMI_CODE_BASE_URL, KIMI_CODE_CUSTOM_HEADERS, KIMI_CODE_OAUTH_HOST, KIMI_MODEL_ADAPTIVE_THINKING, KIMI_MODEL_API_KEY, KIMI_MODEL_BASE_URL, KIMI_MODEL_CAPABILITIES, KIMI_MODEL_DISPLAY_NAME, KIMI_MODEL_MAX_COMPLETION_TOKENS, KIMI_MODEL_MAX_CONTEXT_SIZE, KIMI_MODEL_MAX_OUTPUT_SIZE, KIMI_MODEL_MAX_TOKENS, KIMI_MODEL_NAME, KIMI_MODEL_PROVIDER_TYPE, KIMI_MODEL_REASONING_KEY, KIMI_MODEL_TEMPERATURE, KIMI_MODEL_THINKING_EFFORT, KIMI_MODEL_THINKING_KEEP, KIMI_MODEL_TOP_P, KIMI_OAUTH_HOST, KIMI_REGION, KIMI_REGISTRY_API_KEY, MOONSHOT_API_KEY |
mcode | MAVIS_REGION, MCODE_API_BASE_URL, MCODE_AUTH_BASE_URL, MCODE_AUTH_PROVIDER, MCODE_CLIENT_ID, MCODE_GATEWAY_FORMAT, MCODE_GATEWAY_MODEL, MCODE_GATEWAY_URL, MCODE_PROVIDER_API_KEY, MCODE_REGION, MINIMAX_API_KEY, MINIMAX_CN_API_KEY |
mimo | ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, AZURE_API_KEY, AZURE_RESOURCE_NAME, DEEPSEEK_API_KEY, GOOGLE_CLOUD_PROJECT, GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_VERTEX_LOCATION, GROK_CODE_XAI_API_KEY, MIMOCODE_AUTH_CONTENT, MIMOCODE_CONFIG_CONTENT, MIMO_API_KEY, MIMO_GATEWAY_API, MIMO_GATEWAY_KEY, MIMO_GATEWAY_MODEL, MIMO_GATEWAY_URL, OPENAI_API_KEY, OPENROUTER_API_KEY, XAI_API_KEY, XIAOMI_API_KEY |
omp | ABLITERATION_API_KEY, AIAND_API_KEY, AIMLAPI_API_KEY, AI_GATEWAY_API_KEY, ALIBABA_CODING_PLAN_API_KEY, ALIBABA_TOKEN_PLAN_API_KEY, ANTHROPIC_API_KEY, ANTHROPIC_FOUNDRY_API_KEY, ANTHROPIC_OAUTH_TOKEN, AWS_ACCESS_KEY_ID, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, AWS_SECRET_ACCESS_KEY, AZURE_OPENAI_API_KEY, BAILIAN_TOKEN_PLAN_API_KEY, BASETEN_API_KEY, BIGMODEL_API_KEY, CEREBRAS_API_KEY, CHARM_HYPER_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, CLAUDE_CODE_USE_FOUNDRY, CLINE_API_KEY, CLOUDFLARE_AI_GATEWAY_API_KEY, COMMAND_CODE_API_KEY, COPILOT_GITHUB_TOKEN, COREWEAVE_API_KEY, COREWEAVE_PROJECT, CURSOR_ACCESS_TOKEN, DEEPINFRA_API_KEY, DEEPSEEK_API_KEY, DEVIN_API_KEY, FIREPASS_API_KEY, FIREWORKS_API_KEY, FOUNDRY_BASE_URL, FUGU_API_KEY, GEMINI_API_KEY, GITLAB_TOKEN, GMI_API_KEY, GOOGLE_API_KEY, GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_CLOUD_API_KEY, GOOGLE_CLOUD_LOCATION, GOOGLE_CLOUD_PROJECT, GROK_CODE_XAI_API_KEY, GROQ_API_KEY, HF_TOKEN, HUGGINGFACE_HUB_TOKEN, HYPER_API_KEY, KILO_API_KEY, KIMI_API_KEY, LITELLM_API_KEY, LLAMA_CPP_API_KEY, LM_STUDIO_API_KEY, META_API_KEY, MINIMAX_API_KEY, MINIMAX_CODE_API_KEY, MINIMAX_CODE_CN_API_KEY, MISTRAL_API_KEY, MODEL_API_KEY, MOONSHOT_API_KEY, NANO_GPT_API_KEY, NOVITA_API_KEY, NVIDIA_API_KEY, OLLAMA_API_KEY, OLLAMA_CLOUD_API_KEY, OMP_AUTH_BROKER_TOKEN, OMP_AUTH_BROKER_URL, OPENAI_API_KEY, OPENAI_CODEX_OAUTH_TOKEN, OPENCODE_API_KEY, OPENROUTER_API_KEY, QIANFAN_API_KEY, QWEN_OAUTH_TOKEN, QWEN_PORTAL_API_KEY, SAKANA_API_KEY, SILICONFLOW_API_KEY, SILICONFLOW_CN_API_KEY, SYNTHETIC_API_KEY, TOGETHER_API_KEY, UMANS_AI_CODING_PLAN_API_KEY, VENICE_API_KEY, VERCEL_AI_GATEWAY_API_KEY, VLLM_API_KEY, WAFER_SERVERLESS_API_KEY, WANDB_API_KEY, XAI_API_KEY, XAI_OAUTH_TOKEN, XIAOMI_API_KEY, XIAOMI_TOKEN_PLAN_AMS_API_KEY, XIAOMI_TOKEN_PLAN_CN_API_KEY, XIAOMI_TOKEN_PLAN_SGP_API_KEY, YOLO_AUTO_API_KEY, ZAI_API_KEY, ZENMUX_API_KEY, ZHIPU_API_KEY |
opencode | ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, AZURE_API_KEY, AZURE_RESOURCE_NAME, DEEPSEEK_API_KEY, GEMINI_API_KEY, GITHUB_TOKEN, GOOGLE_API_KEY, GOOGLE_CLOUD_LOCATION, GOOGLE_CLOUD_PROJECT, GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_VERTEX_LOCATION, GOOGLE_VERTEX_PROJECT, GROK_CODE_XAI_API_KEY, MISTRAL_API_KEY, OPENAI_API_KEY, OPENCODE_API_KEY, OPENCODE_AUTH_CONTENT, OPENCODE_CONFIG_CONTENT, OPENCODE_GATEWAY_KEY, OPENCODE_GATEWAY_MODEL, OPENCODE_GATEWAY_NPM, OPENCODE_GATEWAY_URL, OPENCODE_WELLKNOWN, OPENROUTER_API_KEY, XAI_API_KEY |
pi | AI_GATEWAY_API_KEY, ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_OAUTH_TOKEN, ANT_LING_API_KEY, AWS_ACCESS_KEY_ID, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, AWS_REGION, AWS_SECRET_ACCESS_KEY, AZURE_OPENAI_API_KEY, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_BASE_URL, AZURE_OPENAI_DEPLOYMENT_NAME_MAP, AZURE_OPENAI_RESOURCE_NAME, BASETEN_API_KEY, CEREBRAS_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_GATEWAY_ID, COPILOT_GITHUB_TOKEN, DEEPSEEK_API_KEY, FIREWORKS_API_KEY, GCLOUD_PROJECT, GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_CLOUD_API_KEY, GOOGLE_CLOUD_LOCATION, GOOGLE_CLOUD_PROJECT, GROK_CODE_XAI_API_KEY, GROQ_API_KEY, HF_TOKEN, KIMI_API_KEY, META_API_KEY, MINIMAX_API_KEY, MINIMAX_CN_API_KEY, MISTRAL_API_KEY, MOONSHOT_API_KEY, NVIDIA_API_KEY, OPENAI_API_KEY, OPENCODE_API_KEY, OPENROUTER_API_KEY, PI_GATEWAY_API, PI_GATEWAY_KEY, PI_GATEWAY_MODEL, PI_GATEWAY_URL, QWEN_TOKEN_PLAN_API_KEY, QWEN_TOKEN_PLAN_CN_API_KEY, RADIUS_API_KEY, TOGETHER_API_KEY, TYPESAFE_API_KEY, XAI_API_KEY, XIAOMI_API_KEY, XIAOMI_TOKEN_PLAN_AMS_API_KEY, XIAOMI_TOKEN_PLAN_CN_API_KEY, XIAOMI_TOKEN_PLAN_SGP_API_KEY, ZAI_API_KEY, ZAI_CODING_CN_API_KEY |
qwen | ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, ANTHROPIC_MODEL, BAILIAN_CODING_PLAN_API_KEY, BAILIAN_TOKEN_PLAN_API_KEY, GEMINI_API_KEY, GEMINI_MODEL, GOOGLE_API_KEY, GOOGLE_CLOUD_LOCATION, GOOGLE_CLOUD_PROJECT, GOOGLE_GEMINI_BASE_URL, GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_MODEL, GOOGLE_VERTEX_BASE_URL, OPENAI_API_BASE, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL, QWEN_API_KEY, QWEN_BASE_URL, QWEN_CODE_MODEL, QWEN_DEFAULT_AUTH_TYPE, QWEN_MODEL, QWEN_OAUTH_MODELS |
| an ACP CLI | none |
Defined in src/hmz/coganchor/backends.py (Profile.accounts, Profile.hushes, ALIKE), src/hmz/coganchor/agents/base.py (environment, hushed).
Gateways
A gateway account points the CLI at an endpoint speaking that CLI's protocol.
| Backend | Gateway way | Endpoint variable (Profile.endpoint) |
|---|---|---|
claude | anthropic-gateway | ANTHROPIC_BASE_URL |
bedrock-gateway | none (its URL is ANTHROPIC_BEDROCK_BASE_URL) | |
vertex-gateway | none (its URL is ANTHROPIC_VERTEX_BASE_URL) | |
codex | openai-gateway | CODEX_PROVIDER_URL |
cursor-agent | cursor-gateway | none |
dsh | openai-gateway, anthropic-gateway, gemini-gateway | DEEPSEEK_BASE_URL |
litellm | openai-gateway, anthropic-gateway | LITELLM_GATEWAY_URL |
grok | openai-gateway, anthropic-gateway | GROK_XAI_API_BASE_URL |
kimi | openai-gateway, anthropic-gateway, gemini-gateway (and kimi-key) | KIMI_MODEL_BASE_URL |
mcode | openai-gateway, anthropic-gateway | none (mcode provider list --json is the catalogue) |
opencode | openai-gateway, anthropic-gateway, gemini-gateway | OPENCODE_GATEWAY_URL |
mimo | openai-gateway, anthropic-gateway, gemini-gateway | MIMO_GATEWAY_URL |
pi | openai-gateway, anthropic-gateway, gemini-gateway | PI_GATEWAY_URL |
qwen | openai-gateway | OPENAI_BASE_URL |
qwen | anthropic-gateway, gemini-gateway | OPENAI_BASE_URL, which they do not set: their catalogue is the advisory one |
agy | gemini-gateway, openai-gateway | GOOGLE_GEMINI_BASE_URL (neither way sets it: agy models is the catalogue) |
A backend with an endpoint variable has its catalogue read from the endpoint when the account sets it (model catalogues). pi names its models provider/id, which an endpoint's ids do not carry, except on a gateway account, whose every turn is held to the one provider below.
omp has no gateway way and no endpoint variable. An endpoint of its own is a provider in omp's own models.yml (or models.yaml) in its home, which Humanize does not write; the fence lets through the baseUrl of the provider the model is named under (Agents › Oh My Pi).
An opencode gateway account is a provider called gateway in opencode's config, set as OPENCODE_CONFIG_CONTENT (merged over the config files; it replaces an exported OPENCODE_CONFIG_CONTENT). opencode fills each {env:VAR} from the account:
{"provider": {"gateway": {
"npm": "{env:OPENCODE_GATEWAY_NPM}",
"options": {"baseURL": "{env:OPENCODE_GATEWAY_URL}", "apiKey": "{env:OPENCODE_GATEWAY_KEY}"},
"models": {"{env:OPENCODE_GATEWAY_MODEL}": {}}
}}}npm is @ai-sdk/anthropic for anthropic-gateway and @ai-sdk/google for gemini-gateway. The URL includes the API version (…/v1, Gemini's …/v1beta). A turn's model is named bare, as the endpoint lists it, or as gateway/<id>; the driver runs --model gateway/<id> and sets OPENCODE_GATEWAY_MODEL=<id> for that turn.
A mimo gateway account sets MIMOCODE_CONFIG_CONTENT to one provider, humanize, whose adapter, URL, key and model are {env:…} references mimo fills in itself:
{"provider":{"humanize":{"npm":"{env:MIMO_GATEWAY_API}","options":{"baseURL":"{env:MIMO_GATEWAY_URL}","apiKey":"{env:MIMO_GATEWAY_KEY}"},"models":{"{env:MIMO_GATEWAY_MODEL}":{}}}}}A turn under it sets MIMO_GATEWAY_MODEL to the turn's model, without a leading humanize/, and asks for --model humanize/<id>, so every id the endpoint lists can be run. MIMO_GATEWAY_URL is the API base the adapter appends to: …/v1 for OpenAI and Anthropic, …/v1beta for Gemini. Under a gateway account every model is the gateway's, xiaomi/… included. The catalogue is read from <MIMO_GATEWAY_URL>/models as for any endpoint; a Gemini endpoint that does not answer there in OpenAI's shape falls back to mimo models, which lists the account's own humanize/<MIMO_GATEWAY_MODEL>.
A mimo vertex, bedrock or azure account adds no host under a cut network: their hosts are per region or resource.
Codex reads a gateway from configuration, not variables. A turn under a codex openai-gateway account appends, and no config.toml is written:
-c model_provider=humanize
-c model_providers.humanize.name=humanize
-c model_providers.humanize.base_url=<CODEX_PROVIDER_URL>
-c model_providers.humanize.env_key=CODEX_PROVIDER_KEY
-c model_providers.humanize.wire_api=responsesCodex 0.160 speaks only the Responses API (it refuses wire_api = "chat"), so the way asks no protocol. A codex azure account appends the same with Azure's name, endpoint, key variable and API version; its catalogue is codex's own, a resource's /models not being its deployments:
-c model_provider=humanize
-c model_providers.humanize.name=azure
-c model_providers.humanize.base_url=<AZURE_OPENAI_BASE_URL>
-c model_providers.humanize.env_key=AZURE_OPENAI_API_KEY
-c model_providers.humanize.query_params.api-version="<AZURE_OPENAI_API_VERSION>"
-c model_providers.humanize.wire_api=responsesA dsh gateway is a route of the runtime's generic @deepseek-ai/dsh-llm-pi-ai adapter, not the DeepSeek adapter, which speaks DeepSeek's own dialect. A turn under a dsh gateway account adds this to the agent's composition and opens its session on <route> instead of deepseek-official:
- id: llm-pi-ai
name: '@deepseek-ai/dsh-llm-pi-ai'
config:
providers:
<route>:
apiKeyEnv: DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
models:
- id: <the agent's model>
api: <api>| Way | <route> | <api> | Requests go to |
|---|---|---|---|
openai-gateway | gateway | !!js process.env.DSH_GATEWAY_API | {base}/chat/completions or {base}/responses, Authorization: Bearer |
anthropic-gateway | gateway | !!js 'anthropic-messages' | {base}/v1/messages, x-api-key |
gemini-gateway | google (pi-ai's catalogue route) | omitted | {base}/models/<model>:streamGenerateContent, x-goog-api-key |
- The agent's effort does not reach a gateway route: the turn runs at the endpoint's default.
- An agent that may search gets
web_fetchbut noweb_searchunder a gateway account:dsh-web-search-deepseekis not mounted anddsh-tool-webis configured withsearch: false. - A
gemini-gatewayendpoint's model list is not in the shapeGET {base}/modelsis read for, so its catalogue is the DeepSeek one and the model is typed.
Grok Build reads which API a model speaks only from an api_backend on a [model.*] entry of $GROK_HOME/config.toml (the GROK_CONFIG overlay cannot set model). A turn under a grok openai-gateway or anthropic-gateway account merges one entry into the copy of that file in the process's virtual home, keeping everything else in it, and runs with --model naming it; the user's own config.toml is never written:
[model."hmz-<12 hex digits>/<model>"]
model = "<model>"
base_url = "<GROK_XAI_API_BASE_URL>"
api_backend = "<GROK_GATEWAY_API_BACKEND>"
env_key = "XAI_API_KEY"
hidden = trueThe entry is named by a hash of its contents, so the same endpoint, model and API ask for one name. The key is never written; it is read from XAI_API_KEY. An anthropic-gateway turn also sets GROK_CONFIG to {"models": {"extra_headers": {"x-api-key": <key>, "anthropic-version": "2023-06-01"}}}, laid over the GROK_CONFIG the turn would otherwise have (none under an account, which hushes it): grok 1.0.46 drops a model's own headers on grok agent stdio. A turn on another machine is refused for an anthropic-gateway account, and for an openai-gateway account whose anchor drives the target's own CLI or runs its harness on another machine.
pi reads a gateway only from models.json in its home ($PI_CODING_AGENT_DIR, else ~/.pi/agent). Each process under a pi gateway account on this machine starts in a virtual home whose copy of models.json has one provider added, and the turn gets --provider <name>; the user's own models.json is never written:
{
"providers": {
"humanize-<sha256(declaration)[:12]>": {
"baseUrl": "<PI_GATEWAY_URL>",
"api": "<PI_GATEWAY_API>",
"apiKey": "$PI_GATEWAY_KEY",
"models": [{ "id": "<PI_GATEWAY_MODEL>" }]
}
}
}- The key is
$PI_GATEWAY_KEY, which pi resolves from the turn's environment; the secret is written neither to the file nor to the command line. - Everything else in the user's file is kept in the copy, which goes with the process.
- A file that is not plain JSON (pi also takes comments) or has no
providersobject fails the turn withValueErrornaming the entry to add by hand. - With
--provider, pi takes any model id, bare or<name>/-prefixed, as an id of that provider, listed or not. - A gateway turn of an anchored agent raises
ValueErrorbefore it starts: its pi reads the target'smodels.json, which nothing here builds.
Hosts reachable under a cut network
A flow role whose online is NONE is fenced to the hosts backends.reachable(profile, environ) returns, environ being the environment a turn under the account runs with, providers.composed(provider, profile): this process's own, less every variable the account hushes and plus the ones it sets. The flow's fence and the agent's own widening of it both read it there, so a hushed variable opens no host:
Profile.hosts(see Agents › Network hosts);- the host (and
:port, where one is written) of the endpoint variable and of everyambientvariable ending_URL,_BASE,_HOST,_ENDPOINT,_ORIGINor_ISSUER; - for
claudewith one of the switches below set, and forompwithCLAUDE_CODE_USE_FOUNDRYset, the cloud's hosts and the host of the switch's base URL variable.
| Switch | Hosts | Region variable (default) | Base URL variable |
|---|---|---|---|
CLAUDE_CODE_USE_BEDROCK | bedrock-runtime.{r}.amazonaws.com, bedrock.{r}.amazonaws.com, sts.{r}.amazonaws.com | AWS_REGION (us-east-1) | ANTHROPIC_BEDROCK_BASE_URL |
CLAUDE_CODE_USE_VERTEX | {r}-aiplatform.googleapis.com, aiplatform.googleapis.com, oauth2.googleapis.com | CLOUD_ML_REGION (us-east5) | ANTHROPIC_VERTEX_BASE_URL |
CLAUDE_CODE_USE_FOUNDRY | {r}.services.ai.azure.com | ANTHROPIC_FOUNDRY_RESOURCE (none) | ANTHROPIC_FOUNDRY_BASE_URL |
CLAUDE_CODE_USE_MANTLE | bedrock-mantle.{r}.api.aws, sts.{r}.amazonaws.com | AWS_REGION (us-east-1) | ANTHROPIC_BEDROCK_MANTLE_BASE_URL |
CLAUDE_CODE_USE_ANTHROPIC_AWS | aws-external-anthropic.{r}.api.aws, sts.{r}.amazonaws.com | AWS_REGION (us-east-1) | ANTHROPIC_AWS_BASE_URL |
CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD | claude.googleapis.com, oauth2.googleapis.com | none | ANTHROPIC_GOOGLE_CLOUD_BASE_URL |
A region value that is not one DNS label adds no cloud host. A codex bedrock or bedrock-key account adds none either: its endpoint, bedrock-mantle.<region>.api.aws, is not opened. Example: a claude account with ANTHROPIC_BASE_URL=https://gw.example:8443/v1 reaches api.anthropic.com, platform.claude.com, claude.ai and gw.example:8443 (that port only).
Defined in src/hmz/coganchor/backends.py (reachable, _CLOUDS), src/hmz/coganchor/providers/store.py (composed, hushed), src/hmz/coganchor/models.py (_served, _listing).
Through LiteLLM
With the [litellm] extra installed, a CLI can be run as any account litellm has a way in for -- a vendor's key, an OpenAI- or Anthropic-compatible gateway, Bedrock, Vertex or Azure -- whether or not the CLI has a way in for it of its own. Its turns go through a LiteLLM proxy the agent starts on this machine's loopback, which speaks the CLI's protocol on one side and the account's on the other.
| Backend | Ways (backends.routed) | The CLI is pointed at the gateway by (Profile.routes) |
|---|---|---|
claude | litellm-openai-key, litellm-anthropic-key, litellm-gemini-key, litellm-xai-key, litellm-openrouter-key, litellm-deepseek-key, litellm-groq-key, litellm-mistral-key, litellm-openai-gateway, litellm-anthropic-gateway, litellm-bedrock, litellm-vertex, litellm-azure | anthropic-gateway: ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN |
agy | as claude | openai-gateway: AGY_LLM_GATEWAY_URL, AGY_LLM_GATEWAY_API_KEY, AGY_LLM_GATEWAY_WIRE_PROTOCOL=openai |
codex | as claude | openai-gateway: CODEX_PROVIDER_URL, CODEX_PROVIDER_KEY, and its -c model_providers.humanize.* arguments, wire_api=responses; codex asks {base_url}/responses, which the gateway serves with no /v1 |
dsh | as claude | openai-gateway: DEEPSEEK_BASE_URL, DEEPSEEK_API_KEY, DSH_GATEWAY_API=openai-completions |
grok | as claude | openai-gateway: GROK_XAI_API_BASE_URL, XAI_API_KEY, and GROK_GATEWAY_API_BACKEND chat_completions rather than the way's responses. The model is asked for by its hidden [model.*] entry, and a session grok agent opens on another model -- the first of the gateway's catalogue -- is put on it with session/set_model. The entry is in the virtual home's copy of config.toml only. |
kimi | as claude | openai-gateway: KIMI_MODEL_BASE_URL, KIMI_MODEL_API_KEY, KIMI_MODEL_PROVIDER_TYPE=openai |
pi | as claude | openai-gateway: PI_GATEWAY_URL, PI_GATEWAY_KEY, PI_GATEWAY_API (openai-completions); the models.json provider (gateways) lists the turn's model |
qwen | as claude | openai-gateway: OPENAI_BASE_URL, OPENAI_API_KEY, QWEN_DEFAULT_AUTH_TYPE=openai; --auth-type openai |
opencode | as claude | openai-gateway: OPENCODE_GATEWAY_URL, OPENCODE_GATEWAY_KEY, OPENCODE_GATEWAY_NPM (@ai-sdk/openai-compatible), OPENCODE_CONFIG_CONTENT; OPENCODE_GATEWAY_MODEL is the turn's model |
mimo | as claude | openai-gateway: MIMO_GATEWAY_URL, MIMO_GATEWAY_KEY, MIMO_GATEWAY_API (@ai-sdk/openai-compatible), MIMOCODE_CONFIG_CONTENT; MIMO_GATEWAY_MODEL is the turn's model |
mcode | as claude | openai-gateway: MCODE_GATEWAY_URL, MCODE_PROVIDER_API_KEY, MCODE_GATEWAY_FORMAT openai-completions. The way's command is not run: each turn runs mcode provider add --name hmz-PORT-DIGEST --base-url URL --api-format openai-completions --model MODEL --api-key-env MCODE_PROVIDER_API_KEY (no --use; DIGEST is 8 hex of SHA-256 of the gateway's key and the model) for every gateway and model the agent has named, into the copy of config.yaml in the turn's virtual home, and asks for custom_provider:hmz-PORT-DIGEST/MODEL; the account's own config.yaml is never written, and nothing is removed afterwards. A model is required (Unserved). |
Each litellm-<way> asks and sets what litellm's own <way> does (Ways by backend), and is offered only while the gateway can be started here (gateway.gatewayable(): litellm and its proxy's own modules all importable). The pickers list them after the backend's own ways, under the heading LiteLLM; where they are not offered for a backend that names routes, Accounts.wants(cli) is the line that installs the extra (installed.adding("litellm")), said under that heading instead.
| What | How |
|---|---|
| Started | by agent.gateway, the first time a turn needs it: python -m litellm.proxy.proxy_cli --config FILE --host 127.0.0.1 --port PORT, with this interpreter, on a free port, waited for at /v1/models under its own key, past any proxy, for up to 120 s. One per agent; started again for another account or once it has gone. |
| Configured | gateway.configured(provider, key): a model list of PREFIX/* and *, both sent to PREFIX/* with the account's credentials (gateway.credentials), drop_params, Anthropic messages to an OpenAI-shaped provider sent onward as chat completions, and so are Responses API requests to a gateway account's endpoint (use_chat_completions_api), or to a provider litellm knows no Responses API of. PREFIX is gateway.prefix: the vendor of a key way, bedrock, vertex_ai or azure, or a gateway account's LITELLM_GATEWAY_API. |
| Held | the configuration is a file 0600 in a directory 0700 of its own, removed with the gateway; the master key is sk- and 32 random bytes. Neither is on a command line; a failure to start says the proxy's last lines with every secret struck out. |
| The turn | agent.environment() is the routes way answered by the gateway (gateway.spoken): its URL variable the gateway's http://127.0.0.1:PORT (with /v1 after it for opencode, mimo and pi only, whose SDKs add only the operation; every other backend, claude and any CLI that appends /v1/... itself included, gets the bare URL), its secret the master key, plus what that way sets, and 127.0.0.1 and localhost added to NO_PROXY; its args, filled in, are appended (gateway.argued). claude is also given the agent's model as every model it reaches for on its own (ANTHROPIC_DEFAULT_HAIKU_MODEL, _SONNET_, _OPUS_, ANTHROPIC_SMALL_FAST_MODEL); agy is given it as the one model its gateway serves (AGY_LLM_GATEWAY_MODELS), the only one its --model then takes; kimi the agent's model as KIMI_MODEL_NAME, its daemon started again for another; dsh runs on its pi-ai gateway route, at the adapter's own effort, as under its own openai-gateway. agent.hushed() is hushes() and everything the account sets, so none of it reaches the CLI. |
| Fenced | agent.fenced() adds 127.0.0.1:PORT to the hosts. providers.composed adds none of the account's variables, so the account's own endpoint opens no host for the CLI. |
| Anchored | supervised with the harness here: the gateway runs here, its variables are kept here (private), and with net remote 127.0.0.1:PORT is kept local (net_allow). Driven natively, or with the harness elsewhere, the CLI is not on this machine, and the agent is refused the account (Unserved) where it is configured. |
| Stopped | by agent.stop(), and when the agent or the gateway is collected: SIGTERM to its process group, SIGKILL after 5 s, and every child. |
| Models | models.ask(cli, account) reads what is behind the gateway, never the CLI: a gateway account's endpoint, asked as litellm's are, or litellm's own catalogue narrowed to PREFIX/. A model that reasons takes the CLI's whole ladder -- but under agy, which refuses --effort for every model its gateway serves, none, and its driver sends no --effort through a gateway. A bare id is sent to PREFIX/id. |
A turn of the litellm backend itself is sent with the same mapping (gateway.spelled, gateway.credentials): a bare id under any account is sent to that account's provider.
Defined in src/hmz/coganchor/gateway.py, src/hmz/coganchor/backends.py (routed, upstream, Profile.routes), src/hmz/coganchor/agents/base.py (gateway), src/hmz/coganchor/agents/pi.py (PiAgent.stop), src/hmz/coganchor/providers/store.py (routed).
One account, several CLIs
A credential is the vendor's, so an account made for one backend can often run another.
| Call | Behaviour |
|---|---|
serves(one) -> tuple[str, ...] | The other backends one could be copied to: those for which backends.serves(one.env, backend) is not None, whether or not a copy exists. |
backends.serves(env, backend) | env renamed to what backend reads, each variable matched by any alike name in backend's accounts(); None if env is empty or any variable has no name there. |
copies(one, cli, name="") | Writes the renamed env as an account of cli under name (default one.name), overwriting one there. Its way is the first cli way with no argv whose kept asks and sets are exactly those variables, else env. |
- A login account holds files, not variables, so it copies nowhere.
copiesof an accountclicannot run raisesValueError: claude/work cannot be used with codex.- Example: a
claudekeyaccount (ANTHROPIC_API_KEY) servespi,omp,qwen,opencode,mimoandlitellm; theqwencopy is recorded with wayenv, the others withanthropic-key.
The TUI's account form offers an also for <cli> row per backend in serves().
When an account fails
An account does not name another to carry on under. Where a failed turn goes is a fallback chain written against a place (CLI[@ACCOUNT]/MODEL), so moving to another account of the same CLI is a place on that chain, taken in a new conversation. See Agents › When a turn fails.
Placement of the account
| Arrangement | Where the account is used | Provider variables | Provider credential files |
|---|---|---|---|
| No anchor | this machine | set on the turn | answered here by hmz internal cred |
| Supervised anchor | this machine | set on the agent; private, so commands on the target never get them | answered here by the anchor's supervisor |
| Native anchor | the target | sent; hushes() removed there with env -u | written to a private directory on the target for the turn, where a variable of the backend moves their root; otherwise the turn is refused. A sign-in that refreshes itself goes to one turn at a time and comes back refreshed |
| Harness on another machine | the harness machine | not sent | redirected to this machine's paths, which that machine lacks |
See Remote execution › Where the account lives.
Security properties
| Property | Holds |
|---|---|
provider.json and credential files readable by others | never: 0600 files in 0700 directories from creation |
| A secret shown by the TUI | never: the Accounts page shows variable names; secret answers are drawn as bullets |
| Another agent's credentials readable by a turn | never through the redirected paths: each turn is answered from its own account's directory |
| A shell-profile key overriding the account | never: hushes() is removed from the turn |
| A key passed as argv | never: stdin ways write it to the command's stdin |
| A sign-in that refreshes itself held in two places that refresh apart | never, where its directory can be locked: it is used in place by every turn here, or lent to one native turn at a time while no other turn uses it, and written back |
Provider.env values from Python | returned in full to the caller |
API summary
from hmz.coganchor import providers
from hmz.coganchor.providers import loginproviders. | |
|---|---|
Provider(cli, name, way="env", env={}, args=(), made="") | one account; .at, .swaps(), .command(argv), .held() |
LOCAL | "" |
ENV | the env way |
ways(cli) | the backend's ways, then through LiteLLM, env last (not for dsh or litellm) |
routed(provider) | whether a local LiteLLM gateway holds it |
providers(cli="") | every account, or one backend's, by backend then name |
find(cli, name) | one account or None; never None for LOCAL of a known backend |
where(cli, name) | the account's directory |
add(cli, name, way="env", env=None, args=()) | write one |
ready(provider) | make its credential directories |
remove(cli, name) | delete one |
environ(provider) | its variables, {} for None |
env_of(text) | parse NAME=VALUE lines |
filled(text, answers) | substitute {VARIABLE} |
serves(one), copies(one, cli, name="") | alike accounts |
login. | |
|---|---|
way_of(cli, name) | a way by name, or None |
asked(way, given) | variables still unanswered |
make(cli, name, way, answers=None) | an account from answers |
sign_in(provider, way, answers=None) -> int | run the way's command under the account's paths |
| On an agent | |
|---|---|
agent.provider | Provider | None; None for the machine's own account |
agent.node() | the account it is on now, never None |
agent.environment() | provider.env; through LiteLLM, the gateway |
agent.hushed() | hushes() - provider.env; through LiteLLM, hushes() | provider.env |
agent.gateway | the Gateway a turn through LiteLLM goes through, else None |
Hmz().accounts (hmz.runtime.doing.accounts.Accounts) is the same surface as one object, used by the TUI; see SDK:
Hmz().accounts. | |
|---|---|
all(cli=""), find(cli, name), where(cli, name) | listing and locating |
ways(cli), way(cli, name), asks(way, given) | the ways in |
write(cli, name, way="", env=None, args=()) | write an account as given, running nothing |
make(cli, name, way, answers=None), sign_in(provider, way, answers=None) | make from answers; run the way's command |
serves(one), copies(one, cli, name="") | alike accounts |
remove(cli, name) | delete |
env(text), environ(provider) | env_of, environ |
models(cli, provider=""), asked(cli, provider=""), stale(cli, provider=""), ask(cli, provider="", seconds=None) | the account's model catalogue |