> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Modo Servidor (chatcli server)

> Rode o ChatCLI como servidor gRPC: flags, autenticação, TLS e mTLS, health, métricas, logs, limites, a superfície de RPCs, operação e solução de problemas.

`chatcli server` (alias `chatcli serve`) roda o ChatCLI como serviço gRPC. As chaves de API ficam no servidor; os clientes se conectam com [`chatcli connect`](/pt/server/remote-connect), com o [operator](/pt/kubernetes/k8s-operator) ou com qualquer cliente gRPC. O mesmo binário roda no notebook, em container ([Docker](/pt/start/docker-deployment#docker)) e no Kubernetes ([Helm chart](/pt/start/docker-deployment#kubernetes-helm)).

## Início rápido

<Steps>
  <Step title="Suba um servidor local">
    Sem credencial, o servidor só escuta em loopback:

    ```bash theme={"system"}
    export ANTHROPIC_API_KEY=sk-ant-xxx
    chatcli server --provider CLAUDEAI
    ```

    ```text theme={"system"}
    🚀 ChatCLI server listening on 127.0.0.1:50051
    📊 Prometheus metrics on :9090/metrics
    ```
  </Step>

  <Step title="Conecte de outro terminal">
    O listener é texto puro, e o cliente disca TLS a menos que você libere o texto puro explicitamente:

    ```bash theme={"system"}
    CHATCLI_ALLOW_INSECURE=true chatcli connect localhost:50051
    ```
  </Step>

  <Step title="Deixe acessível por outras máquinas">
    Faça bind em todas as interfaces e defina uma credencial, senão o servidor se recusa a subir (veja [Endereço de bind e a regra da credencial](#endereço-de-bind-e-a-regra-da-credencial)):

    ```bash theme={"system"}
    export CHATCLI_SERVER_TOKEN="$(openssl rand -hex 32)"
    CHATCLI_BIND_ADDRESS=0.0.0.0 chatcli server --provider CLAUDEAI \
      --tls-cert server.crt --tls-key server.key
    ```

    ```bash theme={"system"}
    chatcli connect chatcli.example.com:50051 --tls --ca-cert ca.crt --token "$CHATCLI_SERVER_TOKEN"
    ```
  </Step>
</Steps>

## Endereço de bind e a regra da credencial

O endereço de escuta vem de `CHATCLI_BIND_ADDRESS`. Sem ele, o servidor escolhe:

| Onde roda | Detectado por | Bind padrão |
| - | - | - |
| Pod no Kubernetes | `KUBERNETES_SERVICE_HOST` definido (o kubelet injeta) | `0.0.0.0` |
| Qualquer outro lugar (notebook, VM, **Docker**) | — | `127.0.0.1` |

Um `CHATCLI_BIND_ADDRESS` explícito sempre vence. Não existe flag `--bind`.

Em endereço de loopback (`127.0.0.1`, `::1`, `localhost`) o servidor pode rodar sem credencial: a fronteira de confiança é a máquina. Em **qualquer outro endereço**, um servidor sem credencial aceitaria todo chamador como administrador, então ele se recusa a subir e sai com status 1:

```text theme={"system"}
refusing to serve an unauthenticated API on 0.0.0.0: every caller that can reach it would be admitted as an administrator. Set CHATCLI_SERVER_TOKEN (or --token) for a shared token, CHATCLI_JWT_SECRET for HS256 JWTs, CHATCLI_JWT_PUBLIC_KEY for RS256 JWTs, or CHATCLI_SERVER_TLS_CLIENT_CA (--tls-client-ca) for mTLS. To run without authentication, bind loopback instead (CHATCLI_BIND_ADDRESS=127.0.0.1)
```

Qualquer uma destas opções satisfaz a regra:

| Credencial | Configuração | Identidade do chamador |
| - | - | - |
| Token compartilhado | `--token` / `CHATCLI_SERVER_TOKEN` | subject `legacy-token`, role `admin` |
| JWTs HS256 | `CHATCLI_JWT_SECRET` | claims `sub` e `role` do token |
| JWTs RS256 | `CHATCLI_JWT_PUBLIC_KEY` | claims `sub` e `role` do token |
| TLS mútuo | `--tls-client-ca` / `CHATCLI_SERVER_TLS_CLIENT_CA` (com `--tls-cert`/`--tls-key`) | `mtls:<nome do certificado>`, role `CHATCLI_MTLS_ROLE` |

Material JWT configurado que não carrega não conta como credencial. Quando o JWT é a única credencial, o servidor se recusa a subir com `refusing to start: CHATCLI_JWT_PUBLIC_KEY is set but no RSA public key could be loaded: ...` (ou a mensagem equivalente de `CHATCLI_JWT_SECRET`). Quando também há token ou mTLS, o servidor sobe, registra o erro do JWT no log e aceita só as credenciais que funcionam.

<Warning>
  A recusa vai para o log estruturado. Num container, numa unit do systemd ou num pipe o servidor também escreve esse log no **stderr**, então `docker logs`/`kubectl logs` mostram a recusa sem nenhuma configuração extra; num terminal interativo ela vai só para o arquivo de log. Veja [Logs](#logs).
</Warning>

## Flags

| Flag | Variável | Padrão | Descrição |
| - | - | - | - |
| `--port` | `CHATCLI_SERVER_PORT` | `50051` | Porta gRPC |
| `--token` | `CHATCLI_SERVER_TOKEN` | `""` | Token bearer compartilhado. Prefira a variável: flag aparece na lista de processos |
| `--tls-cert` | `CHATCLI_SERVER_TLS_CERT` | `""` | Certificado do servidor (PEM). O TLS exige cert e chave; um sem o outro é recusado na inicialização |
| `--tls-key` | `CHATCLI_SERVER_TLS_KEY` | `""` | Chave privada do servidor (PEM) |
| `--tls-client-ca` | `CHATCLI_SERVER_TLS_CLIENT_CA` | `""` | Bundle de CA a que os certificados de cliente devem encadear; liga o TLS mútuo |
| `--provider` | `LLM_PROVIDER` | primeiro provedor com credenciais | Provedor LLM padrão |
| `--model` | — | modelo padrão do provedor (por exemplo `ANTHROPIC_MODEL`) | Modelo padrão |
| `--metrics-port` | `CHATCLI_METRICS_PORT` | `9090` | Porta HTTP de `/metrics` e `/healthz`; `0` desliga os dois |
| `--enable-reflection` | `CHATCLI_GRPC_REFLECTION` | `false` | Registra o gRPC reflection; veja [gRPC reflection](#grpc-reflection) |
| `--fallback-providers` | `CHATCLI_FALLBACK_PROVIDERS` | `""` | Cadeia de provedores separada por vírgula; veja [Cadeia de fallback](#cadeia-de-fallback) |
| `--fallback-max-retries` | `CHATCLI_FALLBACK_MAX_RETRIES` | `2` | Tentativas por provedor antes do próximo |
| `--fallback-cooldown-base` | `CHATCLI_FALLBACK_COOLDOWN_BASE` | `30s` | Cooldown depois de uma falha |
| `--fallback-cooldown-max` | `CHATCLI_FALLBACK_COOLDOWN_MAX` | `5m` | Teto do cooldown (backoff exponencial) |
| `--mcp-config` | `CHATCLI_MCP_CONFIG` | `""` | JSON dos servidores MCP. `CHATCLI_MCP_ENABLED=true` sem caminho carrega o padrão `~/.chatcli/mcp_servers.json` |
| `--watch-config` | `CHATCLI_WATCH_CONFIG` | `""` | YAML multi-target do watcher; veja [K8s Watcher](/pt/kubernetes/k8s-watcher) |
| `--watch-deployment` | `CHATCLI_WATCH_DEPLOYMENT` | `""` | Um único Deployment a observar |
| `--watch-namespace` | `CHATCLI_WATCH_NAMESPACE` | `default` | Namespace dele |
| `--watch-interval` | `CHATCLI_WATCH_INTERVAL` | `30s` | Intervalo de coleta |
| `--watch-window` | `CHATCLI_WATCH_WINDOW` | `2h` | Janela de retenção |
| `--watch-max-log-lines` | `CHATCLI_WATCH_MAX_LOG_LINES` | `100` | Linhas de log por pod |
| `--watch-kubeconfig` | `CHATCLI_KUBECONFIG` | in-cluster, senão `~/.kube/config` | Kubeconfig do watcher |

Uma flag passada na linha de comando vence a variável. `chatcli server --help` lista as flags.

## Variáveis de ambiente

Tudo o que a CLI local lê (chaves de provedor como `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `*_MODEL`, `*_MAX_TOKENS`, `CHATCLI_CA_BUNDLE`) também configura o servidor. Estas são específicas do modo servidor:

| Variável | Padrão | Significado |
| - | - | - |
| `CHATCLI_BIND_ADDRESS` | `127.0.0.1`, ou `0.0.0.0` no Kubernetes | Endereço de escuta |
| `CHATCLI_JWT_SECRET` | `""` | Segredo HS256. Um valor que seja PEM, ou caminho para arquivo PEM, é tratado como chave RSA (RS256) |
| `CHATCLI_JWT_PUBLIC_KEY` | `""` | Chave(s) pública(s) RS256: PEM inline ou caminho. Vários blocos PEM são todos aceitos. Vence `CHATCLI_JWT_SECRET` |
| `CHATCLI_JWT_ISSUER` | `""` | Se definida, o `iss` do token tem de ser igual |
| `CHATCLI_JWT_AUDIENCE` | `""` | Se definida, o `aud` do token tem de ser igual (uma string) |
| `CHATCLI_MTLS_ROLE` | `user` | Role de quem se identifica só pelo certificado de cliente |
| `CHATCLI_RATE_LIMIT_RPS` | `10` | Requisições por segundo por chamador |
| `CHATCLI_RATE_LIMIT_BURST` | `20` | Burst por chamador |
| `CHATCLI_MAX_RECV_MSG_SIZE` | `52428800` (50 MB) | Maior mensagem aceita, em bytes |
| `CHATCLI_MAX_SEND_MSG_SIZE` | `52428800` (50 MB) | Maior mensagem enviada, em bytes |
| `CHATCLI_MAX_CONCURRENT_STREAMS` | `100` | Streams simultâneos por conexão |
| `CHATCLI_AUDIT_LOG_PATH` | `""` (desligado) | Caminho absoluto da trilha de auditoria; veja [Log de auditoria](#log-de-auditoria) |
| `CHATCLI_HUB_ENABLED` | ligado | `false` desliga os RPCs do [hub de conversas](/pt/gateway/conversation-hub) |
| `CHATCLI_SERVER_PIPELINE` | `false` | `true` serve os [RPCs de pipeline](#rpcs-de-pipeline) |
| `CHATCLI_GATEWAY_IN_SERVER` | `false` | `true` roda o [chat gateway](/pt/gateway/chat-gateway) dentro do processo do servidor |
| `CHATCLI_ALLOW_HTTP_PROVIDERS` | `false` | `true` deixa URLs de provedor enviadas pelo chamador usarem `http://`; veja [Proteção contra SSRF](#proteção-contra-ssrf) |
| `CHATCLI_ENV` | `prod` | `dev` troca para o console colorido de desenvolvimento no stdout; veja [Logs](#logs) |
| `CHATCLI_LOG_STDERR` | auto | `true`/`false` força as linhas JSON de log no stderr ligadas ou desligadas; sem valor, ficam ligadas sempre que o stderr não é um terminal |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
| `LOG_FILE` | `~/.chatcli/app.log` | Arquivo do log estruturado em JSON (`CHATCLI_LOG_FILE` é um alias) |
| `LOG_MAX_SIZE` | `100MB` | Tamanho em que o arquivo de log rotaciona (`CHATCLI_LOG_MAX_SIZE_MB`, em MB, tem precedência; backups, idade e compactação: `CHATCLI_LOG_MAX_BACKUPS`, `CHATCLI_LOG_MAX_AGE_DAYS`, `CHATCLI_LOG_COMPRESS`) |

## Autenticação do servidor

O chamador envia a credencial como metadata gRPC `authorization: Bearer <token>`. `chatcli connect --token` faz isso tanto para o token compartilhado quanto para um JWT.

<Tabs>
  <Tab title="Token compartilhado">
    ```bash theme={"system"}
    export CHATCLI_SERVER_TOKEN="$(openssl rand -hex 32)"
    chatcli server
    ```

    Todo mundo que tem o token é o mesmo principal, `legacy-token`, com role `admin`. Isso também significa que todos dividem um único bucket de rate limit.
  </Tab>

  <Tab title="JWT (HS256 ou RS256)">
    ```bash theme={"system"}
    # HS256: um segredo compartilhado assina e verifica
    export CHATCLI_JWT_SECRET="$(openssl rand -hex 32)"

    # ou RS256: o servidor guarda só a chave pública
    # export CHATCLI_JWT_PUBLIC_KEY=/etc/chatcli/jwt-public.pem

    export CHATCLI_JWT_ISSUER=chatcli          # opcional
    export CHATCLI_JWT_AUDIENCE=chatcli-api    # opcional
    chatcli server
    ```

    O servidor verifica, nesta ordem:

    * o `alg` do cabeçalho é igual ao algoritmo configurado (`HS256` ou `RS256`); `none` e divergências são rejeitados;
    * a assinatura;
    * `exp` é **obrigatório** (token sem ele é rejeitado), com 30 segundos de tolerância de relógio; `nbf` é respeitado quando presente;
    * `iss` e `aud`, só quando `CHATCLI_JWT_ISSUER` / `CHATCLI_JWT_AUDIENCE` estão definidas.

    Claims lidos: `sub` (padrão `jwt-user`), `role`, `email`, `tenant_id`.

    **Gere um token de teste com openssl (HS256):**

    ```bash theme={"system"}
    b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
    HEADER=$(printf '{"alg":"HS256","typ":"JWT"}' | b64url)
    PAYLOAD=$(printf '{"sub":"alice@example.com","role":"user","iss":"chatcli","aud":"chatcli-api","exp":%d}' \
      $(( $(date +%s) + 3600 )) | b64url)
    SIG=$(printf '%s.%s' "$HEADER" "$PAYLOAD" \
      | openssl dgst -sha256 -hmac "$CHATCLI_JWT_SECRET" -binary | b64url)
    JWT="$HEADER.$PAYLOAD.$SIG"

    CHATCLI_ALLOW_INSECURE=true chatcli connect localhost:50051 --token "$JWT"
    ```

    **Ou em Go:**

    ```go theme={"system"}
    claims := jwt.MapClaims{
        "sub":  "alice@example.com",
        "role": "user",
        "iss":  "chatcli",
        "aud":  "chatcli-api",
        "exp":  time.Now().Add(time.Hour).Unix(),
    }
    signed, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
        SignedString([]byte(os.Getenv("CHATCLI_JWT_SECRET")))
    ```

    Token compartilhado e JWTs podem coexistir: o servidor tenta o bearer primeiro como JWT e depois o compara com o token compartilhado.
  </Tab>

  <Tab title="TLS mútuo">
    ```bash theme={"system"}
    chatcli server --tls-cert server.crt --tls-key server.key --tls-client-ca clients-ca.crt
    ```

    Toda conexão precisa apresentar um certificado de cliente que encadeie em `clients-ca.crt`; sem isso o handshake TLS falha (inclusive para os RPCs de health). `--tls-client-ca` sem `--tls-cert`/`--tls-key` é fatal: `FATAL: --tls-client-ca requires --tls-cert and --tls-key`.

    Quem não envia token bearer é identificado pelo certificado: `mtls:<CN>`, caindo para o primeiro URI SAN e depois o primeiro DNS SAN. A role vem de `CHATCLI_MTLS_ROLE` (padrão `user`). Um token bearer, quando enviado, vence a identidade do certificado.

    ```bash theme={"system"}
    CHATCLI_TLS_CLIENT_CERT=alice.crt CHATCLI_TLS_CLIENT_KEY=alice.key \
      chatcli connect chatcli.example.com:50051 --tls --ca-cert ca.crt
    ```
  </Tab>
</Tabs>

### Roles

| Role | Concedida por |
| - | - |
| `admin` | token compartilhado; JWT `role: admin`; nenhuma credencial configurada (loopback) |
| `user` | JWT `role: user` ou `operator`; JWT sem claim `role`; padrão do mTLS |
| `readonly` | JWT `role: readonly` ou `viewer`; **qualquer role não reconhecida** (registrada no log) |

Onde o servidor as aplica:

| RPCs | Regra |
| - | - |
| `RunCoder`, `RunAgent`, `RunPipelineTool`, `ListPipelineTools` | só `admin` |
| `ExecuteRemotePlugin` | `user` ou `admin` |
| `ListRemotePlugins` | `readonly` recebe lista vazia; plugins com nome `_*` só aparecem para `admin` |
| `DownloadPlugin` | `readonly` recebe `PermissionDenied`; um plugin com nome `_*` é `NotFound` para quem não é `admin` |
| RPCs do hub agindo por outro principal, lendo a conversa de outro principal, listando todos os bindings | só `admin` |
| Todos os outros RPCs (prompts, sessões, info do servidor, watcher, alertas, análise) | qualquer chamador autenticado |

### Limitador de falhas de autenticação

Cada host de cliente tem uma cota de autenticações bearer/JWT **que falharam**: rajada de 5, reposta a uma a cada 12 segundos; a tabela é zerada a cada 5 minutos. Só uma autenticação que falha consome a cota (credencial ausente, malformada, errada ou expirada); credenciais válidas nunca são limitadas, então um cliente movimentado, as sondas do operator e um laço de chamadas one-shot passam em velocidade normal. Quando um host esgota a cota, suas chamadas recebem `Unauthenticated: authentication failed` antes de a credencial ser conferida, até uma vaga ser reposta, e o log do servidor registra `auth failure rate limit exceeded`. Chamadores identificados só pelo certificado de cliente não passam por ele.

## TLS

O servidor liga o TLS quando `--tls-cert` e `--tls-key` estão definidos; a versão mínima é **TLS 1.3**. Nenhum dos dois significa texto puro de propósito. Um sem o outro é recusado antes de qualquer coisa subir, em vez de cair num listener em texto puro: `FATAL: --tls-cert foi definido (server.crt) mas --tls-key não: recusando iniciar um listener em texto puro. Defina os dois, ou nenhum para texto puro` (e a mensagem espelhada para chave sem certificado). Certificado que não carrega é fatal e aparece no stderr: `FATAL: TLS certificate load failed: ... (cert=..., key=...)`.

O certificado precisa valer para o nome que os clientes discam. Uma CA privada para testes:

```bash theme={"system"}
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -keyout ca.key -out ca.crt -subj "/CN=chatcli-ca"
openssl req -newkey rsa:2048 -nodes \
  -keyout server.key -out server.csr -subj "/CN=chatcli.example.com"
printf "subjectAltName=DNS:chatcli.example.com,DNS:localhost,IP:127.0.0.1\n" > server.ext
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -extfile server.ext -out server.crt

# Opcional: certificado de cliente para mTLS
openssl req -newkey rsa:2048 -nodes -keyout alice.key -out alice.csr -subj "/CN=alice"
printf "extendedKeyUsage=clientAuth\n" > client.ext
openssl x509 -req -in alice.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 365 -extfile client.ext -out alice.crt
```

O certificado é lido uma vez na inicialização: um certificado renovado só vale depois de reiniciar.

## Credenciais do LLM

O servidor chama o modelo com as próprias chaves, a menos que a requisição traga credenciais. Opções do `chatcli connect`:

| Modo | Flags do cliente | Observações |
| - | - | - |
| Chaves do servidor (padrão) | nenhuma | `LLM_PROVIDER` / `--provider` do servidor e as chaves dele |
| Chave de API do chamador | `--llm-key <chave> --provider <P>` | a chave vai ao servidor só para aquela requisição |
| OAuth local do chamador | `--use-local-auth [--provider CLAUDEAI\|OPENAI\|COPILOT]` | lê `~/.chatcli/auth-profiles.json`; sem `--provider` tenta Anthropic, OpenAI e depois Copilot |
| StackSpot | `--provider STACKSPOT --client-id … --client-key … --realm … --agent-id …` | |
| Ollama | `--provider OLLAMA --ollama-url https://…` | a URL passa pela [checagem de SSRF](#proteção-contra-ssrf): HTTPS e endereço público. Para um Ollama em rede privada, defina `OLLAMA_ENABLED=true` e `OLLAMA_BASE_URL` no **servidor** e conecte só com `--provider OLLAMA` |

A imagem do servidor não inclui o Devin CLI, então `DEVIN` não está disponível como provedor do servidor.

## Roteamento de requisições

Todo RPC de prompt (`SendPrompt`, `StreamPrompt`, `InteractiveSession`, `AnalyzeIssue`, `AgenticStep`) resolve o cliente do modelo do mesmo jeito:

* Uma requisição que encaminha credencial (`--llm-key`, `--use-local-auth`, campos da StackSpot, URL do Ollama) ou que nomeia provedor ou modelo ganha um cliente dedicado só para ela.
* Uma requisição que não nomeia nada nem encaminha nada passa pela [cadeia de fallback](#cadeia-de-fallback) quando há uma instalada; senão, usa o provedor e o modelo padrão do servidor.
* `max_tokens` é o valor da requisição quando definido; senão o override `*_MAX_TOKENS` do provedor (`ANTHROPIC_MAX_TOKENS`, `OPENAI_MAX_TOKENS`, `BEDROCK_MAX_TOKENS`, …); senão o teto do catálogo para o modelo.
* Um 401/403 do provedor nas credenciais **do servidor** reconstrói os provedores uma vez e tenta de novo; as reconstruções são limitadas a uma a cada 30 segundos no processo inteiro. Credenciais encaminhadas pelo chamador nunca são renovadas.
* Uma resposta interrompida por classificador de segurança (`stop_reason: refusal`) é reenviada uma vez para o modelo irmão do mesmo provedor; em stream, só enquanto nenhum texto foi enviado.

As requisições rodam em paralelo, cada uma com seu cliente.

### Cadeia de fallback

```bash theme={"system"}
export ANTHROPIC_API_KEY=sk-ant-xxx OPENAI_API_KEY=sk-xxx GOOGLEAI_API_KEY=xxx
export CHATCLI_FALLBACK_MODEL_OPENAI=gpt-6-sol
chatcli server --provider CLAUDEAI --fallback-providers CLAUDEAI,OPENAI,GOOGLEAI
```

* A cadeia é montada **só** a partir de `--fallback-providers` / `CHATCLI_FALLBACK_PROVIDERS`, nessa ordem. O servidor não acrescenta o `--provider`: coloque você mesmo o primário na frente. O [Helm chart](/pt/start/docker-deployment#fallback-de-provedor) e o `spec.fallback` do operator já colocam o primário na frente para você.
* Cada entrada usa `CHATCLI_FALLBACK_MODEL_<PROVEDOR>` quando definido. Sem ela, o provedor primário (`--provider`) fica com o modelo do servidor (`--model`), e qualquer outro provedor roda o próprio modelo padrão: a variável de modelo dele (por exemplo `OPENAI_MODEL`), depois o padrão embutido. Um provedor de fallback nunca recebe o id de modelo do primário.
* Um provedor cujo cliente não pode ser criado (sem chave) é pulado com um aviso. A cadeia só é instalada com **pelo menos duas** entradas restantes; aí o log mostra `Fallback chain initialized`.
* Ela atende só requisições que não trazem credenciais nem nomeiam provedor ou modelo. A resposta informa o provedor e o modelo que de fato responderam.
* Um `CHATCLI_FALLBACK_PROVIDERS` não vazio é o único interruptor. `CHATCLI_FALLBACK_ENABLED` não é lida pelo servidor, e nem o Helm chart nem o operator a definem; o `/config` a marca como não lida quando ela está definida.

Veja [Fallback de provedores](/pt/providers/provider-fallback) para a classificação de erros e os cooldowns.

## API gRPC

Serviço `chatcli.v1.ChatCLIService` (proto: `proto/chatcli/v1/chatcli.proto` no repositório), mais o padrão `grpc.health.v1.Health`:

| RPC | Tipo | Para quê |
| - | - | - |
| `SendPrompt` | unário | Um prompt, a resposta completa |
| `StreamPrompt` | stream do servidor | Um prompt, a resposta conforme o provedor transmite |
| `InteractiveSession` | stream bidirecional | Uma conversa num único stream |
| `ListSessions`, `LoadSession`, `SaveSession`, `DeleteSession` | unário | Sessões guardadas no servidor |
| `GetServerInfo` | unário | Versão, provedor, modelo, provedores disponíveis, watcher, contagem de plugins/agentes/skills, `pipeline_enabled` |
| `GetWatcherStatus` | unário | Estado do K8s watcher |
| `Health` | unário | Liveness e versão, sem credencial |
| `GetAlerts` | unário | Alertas ativos do watcher |
| `StreamAlerts` | stream do servidor | Alertas do watcher assim que surgem, com heartbeats |
| `AnalyzeIssue` | unário | Análise de causa raiz e ações sugeridas para uma Issue (operator) |
| `AgenticStep` | unário | Um passo do loop de remediação por IA (operator) |
| `ListRemotePlugins`, `ExecuteRemotePlugin`, `DownloadPlugin` | unário / unário / stream do servidor | Plugins instalados no servidor |
| `ListRemoteAgents`, `GetAgentDefinition`, `ListRemoteSkills`, `GetSkillContent` | unário | Agentes e skills do servidor |
| `ResolveActiveConversation`, `NewConversation`, `AppendEvent`, `ReadConversation`, `SubscribeConversation`, `SetBinding`, `ListBindings` | unário; `SubscribeConversation` stream do servidor | [Hub de conversas](/pt/gateway/conversation-hub) |
| `ChatTurn` | unário | Turno de chat com o pipeline completo no servidor |
| `RunCoder`, `RunAgent` | stream do servidor | Loop do coder / agente no servidor |
| `ListPipelineTools`, `RunPipelineTool` | unário | Ferramentas do motor hospedado no servidor |

### Streaming

`StreamPrompt` repassa cada fragmento assim que o provedor o emite. A mensagem final (`done: true`) traz o `usage` e o `stop_reason` do provedor. Quando a rota escolhida não faz streaming, a resposta é gerada numa chamada e enviada como um único chunk.

### Atribuição da resposta e uso de tokens

`SendPrompt`, `AnalyzeIssue`, `AgenticStep` e a mensagem final do `StreamPrompt` trazem um `TokenUsage`, um `stop_reason`, e o `provider` e o `model` que responderam (a cadeia de fallback pode responder com outro provedor que não o padrão):

```protobuf theme={"system"}
message TokenUsage {
  int32 prompt_tokens = 1;
  int32 completion_tokens = 2;
  int32 cache_read_tokens = 3;
  int32 cache_write_tokens = 4;
  int32 reasoning_tokens = 5;
  bool estimated = 6;   // provedor não devolveu usage; contagem derivada do tamanho
  double cost_usd = 7;  // precificado com as mesmas tabelas do /cost
  bool cost_known = 8;  // false quando o modelo não tem preço publicado
}
```

O `chatcli connect` alimenta o rastreador de custo com esse usage, então o `/cost` numa conexão remota precifica tokens reais.

### RPCs de pipeline

`SendPrompt` é um proxy do modelo: com `chatcli connect`, memória, anexos de `/context`, skills, knowledge e compactação rodam na **sua** CLI e o servidor só chama o modelo. Os RPCs de pipeline fazem o **servidor** assumir o turno inteiro, com o mesmo motor que os servidores MCP e ACP via stdio expõem:

```bash theme={"system"}
CHATCLI_SERVER_PIPELINE=true LLM_PROVIDER=CLAUDEAI chatcli server --token "$CHATCLI_SERVER_TOKEN"
```

| RPC | O que roda no servidor | Role |
| - | - | - |
| `ChatTurn` | um turno de chat com o pipeline de enriquecimento (`plain: true` pede o repasse puro); provedor/modelo opcional por turno | qualquer chamador autenticado |
| `RunCoder`, `RunAgent` | o loop do coder / agente com as ferramentas do servidor; o stream traz linhas da transcrição e depois a resposta final | `admin` |
| `ListPipelineTools`, `RunPipelineTool` | as ferramentas admitidas pela policy (`@git`, `@read`, …) | `admin` |

* O motor lê o provedor e o modelo padrão de `LLM_PROVIDER` / `LLM_MODEL`, não de `--provider` / `--model`.
* As sessões ficam no namespace do principal autenticado (`<subject>/<sessão>`), então dois chamadores nunca dividem uma conversa por escolherem o mesmo id.
* Sem `CHATCLI_SERVER_PIPELINE=true` os RPCs devolvem `Unavailable: the pipeline RPCs are not enabled on this server (start it with CHATCLI_SERVER_PIPELINE=true)`.
* `GetServerInfo.pipeline_enabled` informa se eles estão ativos.

<Warning>
  O motor é um ChatCLI dentro do processo do servidor: os workers dele (memória, servidores MCP, scheduler) rodam ali e os turnos são serializados, então chamadores simultâneos entram em fila. Ele não divide o processo com o gateway co-localizado: com `CHATCLI_SERVER_PIPELINE=true` e `CHATCLI_GATEWAY_IN_SERVER=true` juntos, o gateway roda, o pipeline fica desligado e o log diz `Pipeline RPCs disabled: CHATCLI_SERVER_PIPELINE and CHATCLI_GATEWAY_IN_SERVER cannot share one process; run the gateway separately`.
</Warning>

Helm: `pipeline.enabled: true` no chart do servidor. Operator: `spec.pipeline.enabled: true` na Instance.

### RPCs de AIOps

`GetAlerts`, `StreamAlerts`, `AnalyzeIssue` e `AgenticStep` alimentam o pipeline de remediação do [operator](/pt/kubernetes/k8s-operator). Os alertas são os do [K8s watcher](/pt/kubernetes/k8s-watcher#alertas):

```protobuf theme={"system"}
message WatcherAlert {
  string type = 1;            // HighRestartCount, OOMKilled, PodNotReady, DeploymentFailing, JobFailed, CronJobMissed, NodeNotReady, ...
  string severity = 2;        // INFO, WARNING, CRITICAL
  string message = 3;
  string object = 4;          // nome do pod, ou Kind/nome
  string namespace = 5;
  string deployment = 6;
  int64 timestamp_unix = 7;
}

message GetAlertsRequest {
  string namespace = 1;       // filtro opcional
  string deployment = 2;      // filtro opcional
}
message GetAlertsResponse { repeated WatcherAlert alerts = 1; }
```

#### StreamAlerts

```protobuf theme={"system"}
rpc StreamAlerts(StreamAlertsRequest) returns (stream StreamAlertsResponse);

message StreamAlertsRequest {
  string namespace = 1;       // filtro opcional
  string deployment = 2;      // filtro opcional
  bool include_current = 3;   // abre com os alertas ativos agora
}
message StreamAlertsResponse {
  WatcherAlert alert = 1;     // vazio num heartbeat
  bool heartbeat = 2;         // a cada 15s enquanto o watcher está quieto
}
```

* Com `include_current` o stream começa com o que o `GetAlerts` devolveria e depois só chegam alertas novos. O watcher deduplica por tipo e objeto dentro da janela, então cada alerta é enviado uma vez.
* Heartbeats a cada 15 segundos distinguem um watcher quieto de uma conexão morta. Sem watcher, o stream só traz heartbeats.
* Um assinante que estoura o buffer limitado é derrubado com `ABORTED`; reabra com `include_current` para ressincronizar.

#### AnalyzeIssue

```protobuf theme={"system"}
message AnalyzeIssueRequest {
  string issue_name = 1;
  string namespace = 2;
  string resource_kind = 3;
  string resource_name = 4;
  string signal_type = 5;              // error_rate, oom_kill, pod_restart, ...
  string severity = 6;                 // low, medium, high, critical
  string description = 7;
  int32 risk_score = 8;
  string provider = 9;                 // override opcional
  string model = 10;                   // override opcional
  string kubernetes_context = 11;      // status do deployment, pods, eventos, revisões
  string previous_failure_context = 12; // tentativas de remediação anteriores que falharam
}

message AnalyzeIssueResponse {
  string analysis = 1;
  float confidence = 2;                // 0.0 a 1.0
  repeated string recommendations = 3;
  string model = 4;
  string provider = 5;
  repeated SuggestedAction suggested_actions = 6;
  TokenUsage usage = 7;
}
```

### Descoberta de recursos

`ListRemotePlugins`, `ExecuteRemotePlugin` e `DownloadPlugin` expõem os plugins instalados no servidor; `ListRemoteAgents`, `GetAgentDefinition`, `ListRemoteSkills` e `GetSkillContent` expõem seus agentes e skills. O comando `/connect` dentro do REPL registra os plugins do servidor na sua sessão; veja [Conexão Remota](/pt/server/remote-connect#plugins-remotos-sessões-e-status-do-watcher).

## Health checks

| Checagem | Onde | Credencial | Resposta |
| - | - | - | - |
| `grpc.health.v1.Health/Check` | porta gRPC | nenhuma | `SERVING` quando o listener está servindo, `NOT_SERVING` durante o desligamento. Nomes de serviço `""` e `chatcli.v1.ChatCLIService` |
| `chatcli.v1.ChatCLIService/Health` | porta gRPC | nenhuma | status e versão do servidor (o que o `chatcli connect` chama primeiro) |
| `GET /healthz` | porta de métricas (`9090`) | nenhuma | `200 ok`; só quando `--metrics-port` não é `0` |

```bash theme={"system"}
grpc-health-probe -addr=localhost:50051                                  # texto puro
grpc-health-probe -addr=chatcli.example.com:50051 -tls -tls-ca-cert ca.crt  # TLS
curl -s http://localhost:9090/healthz
```

Os RPCs de health pulam a checagem do bearer, mas com mTLS o handshake TLS ainda exige certificado de cliente (`grpc-health-probe -tls-client-cert … -tls-client-key …`). A imagem do container traz o `grpc-health-probe` em `/usr/local/bin/grpc-health-probe`. Probe `grpc` do kubelet não faz TLS, por isso o Helm chart usa `/healthz` e uma checagem TCP.

## Métricas

`--metrics-port` (padrão `9090`) serve métricas Prometheus em `/metrics` (com negociação OpenMetrics) e `/healthz`. O listener de métricas faz bind em **todas as interfaces**, independente de `CHATCLI_BIND_ADDRESS`, e não tem autenticação: coloque firewall ou use `--metrics-port 0`.

| Métrica | Tipo | Labels |
| - | - | - |
| `chatcli_grpc_requests_total` | counter | `method`, `code` |
| `chatcli_grpc_request_duration_seconds` | histogram | `method` |
| `chatcli_grpc_in_flight_requests` | gauge | `method` |
| `chatcli_grpc_stream_messages_sent_total` / `_received_total` | counter | `method` |
| `chatcli_llm_requests_total` | counter | `provider`, `model`, `status` |
| `chatcli_llm_request_duration_seconds` | histogram | `provider`, `model` |
| `chatcli_llm_tokens_used_total` | counter | `provider`, `model`, `type` |
| `chatcli_llm_errors_total` | counter | `provider`, `model`, `error_type` |
| `chatcli_session_active_total` | gauge | — |
| `chatcli_session_operations_total` | counter | `operation` |
| `chatcli_server_info` | gauge (1) | `version`, `provider`, `model` |
| `chatcli_server_uptime_seconds` | gauge | — |
| `chatcli_watcher_*` | | veja [métricas do K8s Watcher](/pt/kubernetes/k8s-watcher#métricas-prometheus-do-watcher) |

Os coletores de runtime Go e de processo (`go_*`, `process_*`) também são registrados.

### Exportação OpenTelemetry (OTLP)

O motor do ChatCLI pode enviar os contadores de sessão para um coletor OpenTelemetry via OTLP/HTTP (JSON), configurado só pelas variáveis padrão do OTel:

```bash theme={"system"}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20token"
export OTEL_SERVICE_NAME="chatcli"          # padrão
export OTEL_METRIC_EXPORT_INTERVAL=60000    # ms, padrão
```

No modo servidor isso cobre os turnos que rodam num motor dentro do processo, ou seja, os [RPCs de pipeline](#rpcs-de-pipeline) e o gateway co-localizado. O tráfego de proxy `SendPrompt`/`StreamPrompt` é medido pelas métricas Prometheus acima. Somas exportadas: `chatcli.llm.tokens`, `chatcli.llm.cost`, `chatcli.context.compactions`, `chatcli.context.compaction_cost`, `chatcli.cache.requests`, `chatcli.cache.storage_cost` e, só com `OTEL_RESOURCE_ATTRIBUTES` contendo `chatcli.session=attr`, `chatcli.session.cost`.

## Limites e keepalive

| Configuração | Padrão | Variável |
| - | - | - |
| Requisições por segundo por chamador | `10` | `CHATCLI_RATE_LIMIT_RPS` |
| Burst por chamador | `20` | `CHATCLI_RATE_LIMIT_BURST` |
| Mensagem máxima recebida / enviada | 50 MB / 50 MB | `CHATCLI_MAX_RECV_MSG_SIZE` / `CHATCLI_MAX_SEND_MSG_SIZE` |
| Streams simultâneos por conexão | `100` | `CHATCLI_MAX_CONCURRENT_STREAMS` |

O rate limiter é um token bucket por chamador que roda **depois** da autenticação. A chave é o subject do chamador: `sub` do JWT, `mtls:<nome>`, `legacy-token` para o token compartilhado (então todos os clientes do token compartilhado dividem **um** bucket), `system` quando não há credencial configurada; os RPCs de health usam o endereço do peer. Acima do limite, uma chamada unária ou um stream falha com `ResourceExhausted: rate limit exceeded, retry after N seconds` e um header `retry-after` com o mesmo N: o tempo para repor um token, arredondado para cima e nunca abaixo de 1 segundo (`1` no padrão de 10 rps).

Keepalive: o servidor aceita pings de cliente a cada 20 segundos ou mais (mesmo sem stream ativo) e manda ping em conexões ociosas a cada 60 segundos, fechando-as depois de 10 segundos sem resposta. O `chatcli connect` e o operator mandam ping a cada 30 segundos.

## Proteção contra SSRF

URLs de provedor enviadas por um **chamador** (por exemplo `--ollama-url`, campos `base_url`, `api_base`, `endpoint`, `url`, `host`, `server_url`, `realm_url`) são checadas antes do uso:

* só `https://`; `http://` exige `CHATCLI_ALLOW_HTTP_PROVIDERS=true` no servidor;
* o host (ou cada endereço para o qual ele resolve) não pode estar em `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, `100.64.0.0/10`, `::1`, `fc00::/7`, `fe80::/10`, `ff00::/8` ou faixas IPv4 mapeadas; não há override para endereços privados;
* hostnames de metadata de nuvem (`metadata.google.internal`, `metadata.goog`, `instance-data`) são recusados.

A configuração do próprio servidor (por exemplo `OLLAMA_BASE_URL`) não passa por essa checagem.

## Log de auditoria

```bash theme={"system"}
export CHATCLI_AUDIT_LOG_PATH=/var/log/chatcli/audit.jsonl   # precisa ser absoluto
```

Cada RPC acrescenta uma linha JSON a uma trilha encadeada por hash e protegida por lock de arquivo (a mesma trilha que o auditor de requisições LLM escreve, então os dois tipos de entrada se intercalam numa única cadeia verificável). Campos: `timestamp`, `kind` (`grpc`), `request_id`, `action` (nome do RPC), `actor` (`user:<subject>` ou `anonymous`), `role`, `ip`, `client_id`, `method`, `resource`, `result` (`success`, `error`, `denied`), `duration`. Caminho relativo desliga o arquivo com um erro no log. As entradas também vão para o log estruturado, no logger `audit`.

## Logs

O servidor usa um logger estruturado em JSON:

* Toda entrada vai para o arquivo rotativo `LOG_FILE` (padrão `~/.chatcli/app.log`; `CHATCLI_LOG_FILE` é um alias).
* `chatcli server` e `chatcli gateway` também escrevem as mesmas entradas como **linhas JSON no stderr sempre que o stderr não é um terminal** — container, unit do systemd, pipe — então `docker logs`, `kubectl logs` e coletores de log recebem as recusas de inicialização e tudo o que vem depois sem configuração extra. Num terminal interativo o arquivo é o único destino. `CHATCLI_LOG_STDERR=true|false` força de um jeito ou de outro. O stdout fica livre para os transportes stdio.
* `CHATCLI_ENV=dev` troca para o console de desenvolvimento (colorido, no stdout) mais o arquivo; as linhas JSON no stderr não são duplicadas nesse caso.
* `LOG_LEVEL` define o nível. Rotação: `LOG_MAX_SIZE` (ou `CHATCLI_LOG_MAX_SIZE_MB`, em MB), `CHATCLI_LOG_MAX_BACKUPS` (padrão 3), `CHATCLI_LOG_MAX_AGE_DAYS` (padrão 28), `CHATCLI_LOG_COMPRESS` (padrão `true`, gzip). São essas as variáveis que o `spec.features.logRotation` da Instance do operator define; o Helm chart do servidor as define pelo bloco `logging` (20 MB, 3 backups por padrão, para o log caber no volume de dados de 200Mi do pod; veja [Logs, health e métricas](/pt/start/docker-deployment#logs-health-e-métricas)).

```bash theme={"system"}
LOG_LEVEL=debug chatcli server 2>server.log      # linhas JSON no stderr, já que ele não é um terminal
CHATCLI_ENV=dev LOG_LEVEL=debug chatcli server    # console colorido para desenvolvimento
```

Nas imagens de container o home é efêmero e a imagem distroless não tem shell para ler um arquivo; lá, o que se lê é o stderr.

## gRPC reflection

O reflection exige a flag `--enable-reflection` e `CHATCLI_GRPC_REFLECTION=true`. A flag assume o valor de `CHATCLI_GRPC_REFLECTION` como padrão, então só a variável já liga (é o que o `server.grpcReflection` do chart e o `spec.server.security.enableReflection` da Instance definem). As chamadas de reflection exigem a mesma credencial de qualquer RPC:

```bash theme={"system"}
CHATCLI_GRPC_REFLECTION=true chatcli server --token "$CHATCLI_SERVER_TOKEN"
grpcurl -plaintext -H "authorization: Bearer $CHATCLI_SERVER_TOKEN" localhost:50051 list
```

Deixe desligado em produção.

## Múltiplas réplicas

O gRPC mantém uma conexão HTTP/2 aberta, então um Service ClusterIP prende cada cliente a um pod. Com mais de uma réplica, use um Service headless: os clientes resolvem `dns:///` para cada endereço de pod e balanceiam em round-robin. Helm: `service.headless: true`; o operator muda para headless sozinho quando `spec.replicas > 1`. Sessões, o banco do hub e a trilha de auditoria são por pod, a menos que o armazenamento seja compartilhado. Com o Helm chart, várias réplicas no volume de sessões `ReadWriteOnce` padrão só funcionam num nó; veja [Rollouts no volume de sessões](/pt/start/docker-deployment#rollouts-no-volume-de-sessões).

## Operando o servidor

<AccordionGroup>
  <Accordion title="Rotacionar o token compartilhado">
    O token é lido na inicialização. Troque e reinicie:

    * binário: defina o novo `CHATCLI_SERVER_TOKEN` e reinicie o processo;
    * Helm com `server.token`: `helm upgrade … --reset-then-reuse-values --set server.token="$(openssl rand -hex 32)"`; a anotação de checksum do Secret recria os pods;
    * Helm com `secrets.existingSecret`: atualize o Secret e rode `kubectl -n chatcli rollout restart deploy/chatcli`.

    Os clientes recebem `authentication failed` até usarem o token novo. Para evitar uma virada brusca, adicione JWTs antes (as duas credenciais funcionam ao mesmo tempo) e aposente o token compartilhado depois.
  </Accordion>

  <Accordion title="Rotacionar chaves JWT">
    * **HS256**: um segredo assina e verifica; trocar `CHATCLI_JWT_SECRET` invalida todo token emitido no momento do restart. Mantenha os tokens de vida curta.
    * **RS256**: `CHATCLI_JWT_PUBLIC_KEY` pode ter vários blocos PEM, e um token verificado por qualquer um deles é aceito. Acrescente a chave pública nova ao lado da antiga, reinicie, passe o emissor para a chave privada nova e remova a chave antiga quando os tokens antigos expirarem.
  </Accordion>

  <Accordion title="Renovar certificados TLS">
    O certificado e o bundle de CA de clientes são carregados na inicialização. Depois de renovar os arquivos (ou o Secret), reinicie o servidor. O operator recria os pods da Instance quando um Secret referenciado muda; com o Helm chart rode `kubectl rollout restart`.
  </Accordion>

  <Accordion title="Atualizar">
    * Binário: `/update` dentro do ChatCLI ou o gerenciador de pacotes, e reinicie o servidor.
    * Imagem: fixe a tag (`ghcr.io/diillson/chatcli:1.214.0`) e troque de propósito; `latest` se move.
    * Helm: `helm upgrade chatcli oci://ghcr.io/diillson/charts/chatcli --version 1.214.0 -n chatcli --reset-then-reuse-values`.

    `GetServerInfo` e `chatcli_server_info{version=…}` informam a versão em execução.
  </Accordion>
</AccordionGroup>

## Solução de problemas

| Sintoma (texto exato) | Causa | Correção |
| - | - | - |
| Processo ou container sai com status 1 e não imprime nada | O stderr é um terminal (o log foi só para o arquivo), ou `CHATCLI_LOG_STDERR=false` | Leia o `LOG_FILE`, ou rode com `CHATCLI_LOG_STDERR=true`; num container a recusa já está no `docker logs`/`kubectl logs` |
| `refusing to serve an unauthenticated API on 0.0.0.0: …` | Bind acessível (Kubernetes, ou `CHATCLI_BIND_ADDRESS=0.0.0.0`) sem credencial | Defina `CHATCLI_SERVER_TOKEN`, material JWT ou CA de cliente; ou faça bind em `127.0.0.1` |
| `refusing to start: CHATCLI_JWT_PUBLIC_KEY is set but no RSA public key could be loaded: …` | Caminho errado ou não é chave pública PEM, e o JWT é a única credencial | Corrija a chave (PEM `PUBLIC KEY`, `RSA PUBLIC KEY` ou `CERTIFICATE`) |
| `FATAL: TLS certificate load failed: … (cert=…, key=…)` | Cert ou chave ausente, ilegível ou sem par | Confira os caminhos e se a chave corresponde ao certificado |
| `FATAL: --tls-client-ca requires --tls-cert and --tls-key` | mTLS sem TLS no servidor | Adicione certificado e chave do servidor |
| `FATAL: --tls-cert foi definido (…) mas --tls-key não: recusando iniciar um listener em texto puro` (ou `--tls-key foi definido … mas --tls-cert não`) | Metade de um par TLS | Defina os dois caminhos, ou nenhum para texto puro |
| Cliente: `transport: authentication handshake failed: tls: first record does not look like a TLS handshake` | Cliente disca TLS, servidor é texto puro | `CHATCLI_ALLOW_INSECURE=true`, ou ligue TLS no servidor |
| Cliente: `error reading server preface: remote error: tls: certificate required` | O servidor exige mTLS e o cliente não enviou certificado | Defina `CHATCLI_TLS_CLIENT_CERT` e `CHATCLI_TLS_CLIENT_KEY` e use `--tls` |
| Cliente: `x509: certificate signed by unknown authority` | Certificado do servidor não confiável | `--ca-cert ca.crt` |
| Cliente: `x509: certificate is valid for …, not …` | O nome discado não está nos SANs | Disque um nome do certificado, ou reemita com esse SAN |
| Cliente: `connect: connection refused` | Servidor parado, porta errada, ou bind no loopback de outro host/container | Confira `CHATCLI_BIND_ADDRESS` e a porta |
| `Connected to ChatCLI server (version: …, provider: remote, model: remote)` e depois `Unauthenticated desc = authentication failed` | Token errado ou ausente (o health check não precisa de token, o `GetServerInfo` precisa) | Passe o `--token` / `CHATCLI_REMOTE_TOKEN` correto |
| `Unauthenticated desc = authentication failed` com um token que funciona no resto do tempo; o log do servidor mostra `auth failure rate limit exceeded` | Algo no mesmo host de cliente falhou a autenticação mais de 5 vezes num minuto (um token errado em outro script, um JWT expirado) e esgotou a cota de falhas do host | Corrija o chamador que falha; a cota repõe uma vaga a cada 12 s e a tabela zera a cada 5 minutos |
| `Unauthenticated desc = token expired` | `exp` do JWT no passado (além dos 30 s de tolerância) | Emita um token novo; confira os relógios |
| `Unauthenticated desc = JWT verification material failed to load; the server accepts no credentials until it is fixed` | A chave JWT não carregou | Corrija a chave e reinicie |
| `ResourceExhausted desc = rate limit exceeded, retry after 1 seconds` | Chamador acima de `CHATCLI_RATE_LIMIT_RPS`/`BURST`; quem usa o token compartilhado divide um bucket | Aumente os limites ou dê a cada chamador seu próprio `sub` de JWT |
| `PermissionDenied desc = coder, agent and tool runs execute on the server and require the admin role` | Chamador JWT/mTLS sem `admin` num RPC de execução do pipeline | Use uma credencial admin |
| `Unavailable desc = the pipeline RPCs are not enabled on this server (start it with CHATCLI_SERVER_PIPELINE=true)` | Pipeline desligado | Defina `CHATCLI_SERVER_PIPELINE=true` (e não `CHATCLI_GATEWAY_IN_SERVER`) |
| `invalid provider configuration: provider_config[base_url]: non-HTTPS provider URLs are blocked; set CHATCLI_ALLOW_HTTP_PROVIDERS=true to allow` | O chamador mandou uma URL `http://` (por exemplo `--ollama-url`) | Use HTTPS, defina a variável no servidor, ou configure a URL no servidor |
| `… resolves to blocked IP …` | URL do chamador aponta para endereço privado | Configure esse endpoint no servidor (`OLLAMA_BASE_URL`) |
| `Fallback chain initialized` nunca aparece | Menos de dois provedores da lista têm credenciais válidas | Adicione chaves ou mais provedores |
| Container `unhealthy` com TLS ligado | O `HEALTHCHECK` da imagem testa texto puro | Substitua por `grpc-health-probe -tls …` |

## Próximos passos

<CardGroup cols={2}>
  <Card title="Conexão Remota" icon="plug" href="/pt/server/remote-connect">
    Conecte ao servidor
  </Card>

  <Card title="Docker e Kubernetes" icon="docker" href="/pt/start/docker-deployment">
    Rode o servidor em container ou com Helm
  </Card>

  <Card title="K8s Watcher" icon="binoculars" href="/pt/kubernetes/k8s-watcher">
    Contexto do Kubernetes em todo prompt
  </Card>

  <Card title="K8s Operator" icon="dharmachakra" href="/pt/kubernetes/k8s-operator">
    Instances gerenciadas e AIOps
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.