Remote & Cross-Device Connections
Applies to: ChengOS v0.2.0+ | Last verified: 2026-09-25 | Source:
crates/cheng-cli/src/config.rs,crates/cheng-cli/src/main.rs,crates/cheng-cli/src/auth,crates/cheng-local-executor
The cheng CLI does not have to run on the same machine as the ChengOS server. When it does
not, one question decides everything else: when a workflow reads a file, whose disk is it?
Two workspace modes
| Mode | File tools run | Sees |
|---|---|---|
server_local | On the API host | The server's filesystem |
client_local | In this CLI process | Your directory |
client_local is what makes a remote server useful for local work: the server orchestrates,
your machine executes, and your files never leave it.
The default is chosen for you
If you do not specify a mode, the CLI derives it from the server URL's host:
localhost | 127.0.0.1 | ::1 → server_local
anything else → client_local
That default is right almost always. A local server sharing your filesystem should just use it; a remote server obviously cannot.
Override per session with --workspace-mode server_local|client_local, or permanently with
workspace_mode in the config file. An unsupported value is rejected outright rather than
silently falling back.
cheng back is always client_local and deliberately has no --workspace-mode flag, even
when the server is on localhost: the product promise is that the selected CLI process owns and
serves its launch directory.
Connecting
cheng chat --server https://chengos.example.com
Use the server's public HTTPS address. A hardened server binds its API to 127.0.0.1, so
http://<host>:19225 is unreachable from another machine by design; the reverse proxy forwards
/api and /ws on 443.
Precedence, highest first: command-line flags → environment variables → the config file →
built-in defaults. The environment variables are CHENG_SERVER_URL and CHENG_TOKEN; the
default server is http://localhost:19225.
The config file lives at $CHENG_CONFIG or ~/.config/cheng/config.json:
{
"server_url": "https://chengos.example.com",
"token_file": "~/.config/cheng/token",
"default_workspace": "Default Workspace",
"default_workflow": "main_chat",
"request_timeout_secs": 30,
"auth_storage": "auto",
"workspace_mode": "client_local",
"workspace_tools": { "shell_enabled": true, "git_enabled": true }
}
Credentials
Bearer tokens are never echoed, never written to REPL history, and never written to project
files. Persisting one is an explicit choice, made in the user-scoped config file — or better,
via token_file, a pointer to a file whose trimmed contents are the token.
Storage policy (auth_storage, or --auth-storage):
| Policy | Stores the credential |
|---|---|
auto (default) | OS keyring; on failure, ask on a TTY or error when non-interactive |
keyring | Require the OS credential service |
host-bound | AES-256-GCM file encrypted with a host/user-derived key — transparent, no master password |
encrypted-file | AES-256-GCM file encrypted with a master password |
file | Plaintext file — requires explicit opt-in |
none | In-memory only; lost on exit |
host-bound is the pragmatic choice on a headless box: encrypted at rest, no keyring daemon, no
password prompt in a script.
Note that auto errors rather than downgrading when there is no TTY. A CI job with no
keyring fails loudly instead of quietly writing a plaintext token.
Credentials are server-scoped, so one machine can hold credentials for several ChengOS
servers and switching --server switches identity with it.
Local tools in client-local mode
In client_local mode the CLI executes workspace tools itself, through
cheng-local-executor — the same execution shell used by other local clients, so file and
process semantics do not drift between them.
The workspace_tools config section controls what is permitted:
| Setting | Default | Meaning |
|---|---|---|
shell_enabled | on | Allow shell commands |
shell_allow_shell_string | off | Allow raw sh -c strings — high risk |
shell_default_timeout_ms / shell_max_timeout_ms | Command timeouts | |
shell_env_allowlist | empty | Extra environment variables commands may set |
shell_deny_patterns | empty | Extra denied command substrings, defence in depth |
git_enabled | on | Git operations |
tests_enabled | on | Test runner |
markdown_enabled | on | Markdown tooling |
preview_enabled | on | Preview |
code_index_enabled | on | Code index |
shell_allow_shell_string is off by default because a raw shell string defeats argument-level
policy: once the command is one opaque string, the deny patterns are the only line left. Turn it
on only when you genuinely need shell features such as pipes.
File changes are decided by the server, described by you. Before a mutating file operation
runs, the server's approval policy asks the executor that owns the files for a preflight: a
side-effect-free description of the planned change (canonical paths, whether targets exist,
sizes, hashes). In client_local mode that executor is this CLI. Small, scoped, undoable changes
then run without a prompt; everything else asks. The facts are pinned, and the CLI refuses the
call with stale_precondition if a file changed in between. An older CLI that cannot answer the
preflight still works, but every file change asks for approval (preflight_unavailable). See
Tool-call Approval Policy.
Read-only sessions are the safest way to point the CLI at a directory you do not want touched:
cheng chat --read-only --cwd ~/projects/my-repo
Sessions across devices
cheng sessions # your CLI sessions on the server
cheng chat --resume <session-id> # rejoin one
cheng history # durable conversations, incl. paused work
cheng chat --resume-conversation <uuid>
Sessions live on the server, so a session started on your laptop is listed from your
desktop. --resume-conversation exists for when the old CLI session has expired but the
conversation is still there; it conflicts with --resume, --workspace, and --workflow
because those would contradict the conversation's own binding.
Resuming a client-local session checks that the stored root label matches your current directory — a resumed session should serve the same tree it was serving before. Labels longer than the server's display cap are stored truncated with a leading ellipsis, and the comparison accounts for that.
Background agents
cheng back # serve this directory as a client-local workspace
cheng ps [-a] # list background agents
cheng logs <pid|session> # where its logs go
cheng stop|restart|rm <id>
cheng prune # clear stopped and stale metadata
cheng back runs headless and serves the current directory to the Web UI: you keep working
in the browser while your files stay on your machine. --status, --stop, and --logs act on
the agent recorded for the current directory, which is usually easier than looking up a pid.
To serve several projects from one agent, declare the roots — on the command line with
repeatable --root PATH[:ro], or once in the config file:
{ "back_roots": ["/home/me/works/api", "/home/me/works/docs:ro"] }
Use absolute paths in the config file. Each root gets its own sandbox and session; roots must not nest. --root overrides
back_roots, and with neither the launch directory is served.
A remote setup that works
// ~/.config/cheng/config.json
{
"server_url": "https://chengos.example.com",
"auth_storage": "host-bound",
"workspace_mode": "client_local",
"default_workspace": "Default Workspace",
"default_workflow": "main_chat",
"workspace_tools": {
"shell_enabled": true,
"shell_allow_shell_string": false,
"git_enabled": true,
"tests_enabled": true
}
}
Then cheng chat in any project directory connects to the remote server while executing file
and shell work locally.
Troubleshooting
| Symptom | Cause |
|---|---|
| File tools see the wrong files | Wrong workspace mode — check the auto-selection rule above |
| Auth fails in CI | auto errors without a TTY; use host-bound or CHENG_TOKEN |
| Shell commands are refused | shell_enabled off, a deny pattern matched, or the session is --read-only |
--resume refuses | The stored root does not match your current directory |
cheng --version differs from the server | It reports the ChengOS release version; a mismatch means genuinely different builds |
| Connection refused on port 19225 | The server's API is loopback-only; use its HTTPS address |
| Every small edit asks for approval | This CLI predates preflight (preflight_unavailable); upgrade it |
cheng back refuses the roots | Two roots nest; serve their common parent as one root |
Next steps
- Using the CLI — commands and the REPL.
- Installing the CLI — getting the binary.
- Authentication — the tokens behind all of this.