# Mobile AI Workspace Fast Track A deployment workbook for a persistent desktop, local models and explicit integrations. Prepared October 7, 2026 UTC. Companion article: https://jwatte.com/blog/mobile-ai-workspace-lessons/ ## What this file is This is a reusable implementation and acceptance plan, not a blind installer. It separates lessons observed in one installation from recommended checks for your own deployment. Commands are small examples, not permission to change a production machine indiscriminately. The companion evidence summary records the original test scope: https://jwatte.com/downloads/mobile-ai-workspace-evidence.json A lightweight CLI inventory helper is provided at https://jwatte.com/downloads/mobile-ai-workspace-probe.py . Read it before running. The helper does not request software installation, login, deployment, service restarts, credential reads or model prompts. Review the executables it invokes: a version manager shim or CLI can have its own behavior. ## 1. Write down the actual target Record the server name, operator account, operating system, architecture, available memory, CPU quota, disk capacity and current backup arrangement. State whether you are entering the host or a desktop container. A container hostname and its filesystem are not the host merely because its terminal looks familiar. Use an existing administrative access path. Do not close your last working SSH session while changing remote access. Do not introduce public desktop, model, database or browser debugging ports as a shortcut. A reasonable first milestone is one protected desktop, one repository and one harmless task. More applications do not compensate for an untested login or missing recovery copy. ### Read only inventory ```bash hostname id -u uname -m cat /etc/os-release free -h df -h ss -lnt ``` These outputs can reveal host details. Keep them private unless you deliberately sanitize them. ### Project record ```json { "project": "YOUR_PROJECT", "host": "YOUR_ENROLLED_HOST", "host_project_directory": "/srv/ai-workspace/projects/YOUR_PROJECT", "desktop_project_directory": "/workspace/YOUR_PROJECT", "approved_actions": ["read", "test", "prepare_preview"], "production_approval_required": true, "operator": "YOUR_ACCOUNT", "status": "not_yet_verified" } ``` Replace the example paths with your actual mounted directories. Never place passwords or bearer tokens in this record. ## 2. Separate replaceable software from retained state A reusable design keeps software in a reproducible image and mutable work outside that image. The names below are a recommended starting layout, not paths secretly created by this workbook. ```text ai-workspace/ projects/ working copies and reviewed source inbox/ validated incoming files state/ application databases and profile volumes secrets/ protected runtime credentials models/ downloaded local model assets releases/ manifests and exact image references evidence/ test results and operation receipts ``` For a new directory in your own home, this creates only directories: ```bash umask 077 mkdir -p "$HOME/ai-workspace"/{projects,inbox,state,secrets,models,releases,evidence} ``` Do not recursively change ownership of an existing data tree without checking its service users. Match the volume owner and the container's documented UID requirements. | Service | State to preserve | Separate concern | |---|---|---| | Webtop | `/config` profile | Base image, mobile interface and toolchain locks | | Project tools | Shared working directories | Git remotes, permissions and deployment account | | Ollama | Model storage | Context, queue and memory settings | | Open WebUI | Application data store | User login, provider connection and uploads | | Qdrant | `/qdrant/storage` and supported snapshots | Model identity, vector dimensions, collection aliases and access keys | | n8n | `/home/node/.n8n` or its configured database | The same encryption key and workflow ownership | | Job runner | Receipts and results | Allowed tasks and duplicate request protection | An image rollback is not automatically a database rollback. Check migration compatibility before running an older application against newer state. Never use `docker compose down -v` as a routine update step. ## 3. Establish access before adding automation Choose your identity gateway and HTTPS entry. Bind host service ports to loopback when they exist only for the local reverse proxy. Keep private service networks explicit. A host loopback binding does not stop another container on the same Docker network from reaching the service. Do not mount the host Docker socket into a general browser desktop. Keep normal browser sandboxing. Do not run an unrestricted root agent behind a convenient password prompt. Verify these separately: 1. An unauthenticated external request is refused. 2. The intended owner can complete a fresh login from outside the server. 3. The local application also applies its intended session checks. 4. Expiration returns the user to the correct task rather than an endless loop. 5. Another tab cannot silently acquire input control. A 403 from a probe proves that probe was refused. It does not prove that an authorized person can finish the login. ### Account approval links Render provider approval links as ordinary anchors and readonly text fields on an authenticated page. Do not make the user copy text out of a video frame. Allow only the intended provider hostname and scheme. HTML escape the displayed URL and code. Keep expiry, account scope and host versus desktop identity visible. Do not log approval URLs, codes or callback tokens. Start authorization only after an owner action with session and request verification. The clipboard button should fall back to native text selection. A browser can refuse programmatic clipboard access even when the page itself is working. ## 4. Add a mobile interface that preserves desktop stability Keep four primary destinations easy to reach: Apps, Keyboard, Files and More. Use direct web pages for tasks such as project questions and job status. Retain the full desktop for tasks that require it. Keep local zoom distinct from remote resolution. Set the initial remote size before the stream starts. Do not turn each keyboard or browser toolbar movement into a new Linux display size. On Safari, test touch input and realistic device scale, not only a narrow viewport. WebKit automation supplements physical iPhone and iPad tests; it does not replace them. ### Readiness states ```text Waiting for authorization Connecting transport Waiting for rendered output Ready for input Disconnected with previous frame marked not live Reconnecting with bounded attempts Explicit action required ``` A socket opening does not establish that the decoder produced a frame. Record safe diagnostics such as close code, codec selection and retry count. Do not capture screen contents or clipboard text in ordinary diagnostics. Offer a compatibility path when a codec fails. The repair used JPEG in the compatibility path alongside other fixes. Its individual contribution was not isolated, and it was not established as universally better for motion or bandwidth. Preserve the option that works on other clients. ### Keyboard and input contract Use a local compose field for long text. Preserve it across in page reconnection, but do not quietly store sensitive drafts in browser local storage. Offer Esc, Tab, arrows and clearly indicated temporary modifiers. Provide a release all action for stuck keys and pointer buttons. Treat multiline terminal paste as an action that can execute commands. Require explicit review and do not append Enter automatically. Distinguish controlling the remote application from moving the local viewport. Use an explicit grab, drop and cancel interaction rather than relying only on an invisible gesture. ### Controller ownership Issue a short lived session bound to the authenticated owner. Enforce one controller at the server, not only in JavaScript. Require an explicit takeover and invalidate the old controller's input authority. If a WebSocket handshake does not carry the browser's cached application credential, use a reviewed session authorization design. Do not solve that problem by making the stream public. Check origin, session, owner and lease. Avoid credentials in URLs and ordinary logs. ## 5. Make file delivery an explicit operation Use separate inboxes, not arbitrary paths supplied by a web page. A project inbox should not silently overwrite a live Git checkout. For each upload record the owner, receipt, original name, saved name, destination, expected length, received bytes and checksum. Validate filenames and reject traversal, control characters and hidden credential filenames. For resume, validate the already received prefix. A repeated chunk or completion request should return the same result, not append or copy again. Reject an incomplete completion. Show the actual saved path only to the authorized user. Cancel should delete only an unfinished transfer, not an already completed file. Browser download and the device share sheet need their own success and cancellation checks. Do not report that a file was saved on a phone merely because the server sent a response. ## 6. Connect local models through an explicit application route Start with a model that fits the real CPU, memory and latency budget. Model file size, runtime memory and interactive speed are different measurements. Leave headroom for the desktop, database, embedding process, build tools and operating system. The observed Hermes project route used an 8192 token context, bounded generation and source retrieval. Treat that as one tested starting point, not a prescription for every model or server. Inside Docker, a service name reaches another service only on an appropriate shared network. `127.0.0.1` inside a container refers to that container, not the host and not a neighboring model service. This is a route fragment, not a complete authenticated gateway deployment: ```yaml model_list: - model_name: local-general litellm_params: model: ollama_chat/hermes3:8b api_base: http://ollama:11434 ``` Do not label that general model project aware. Add and test retrieval explicitly. Keep application gateway credentials distinct from provider master keys. Never put a gateway master key in a public browser bundle. Keep native subscription work separate from provider API routing. Verify the current account and plan rules. Do not erase an entire Claude configuration file merely to switch routes. Remove only the specific unwanted route overrides after review. A local CLI can still use a hosted model and remain subject to provider limits. ## 7. Build retrieval around current evidence Keep original notes, screened derivatives and indexes separate. Preserve original source paths during migration, and verify the approved export with individual file hashes. Exclude credentials, browser tokens and unsupported material rather than quietly treating everything as searchable. Give every excerpt a source identifier, source category, extracted line range and content hash. Keep live operational facts ahead of older copies when answering a current state question. Preserve history for historical questions. Exact quotations help auditing. They do not prove that a model's interpretation follows from the quote. Include unsupported and ambiguous questions in the tests. ### Qdrant acceptance Record the embedding model, dimensions, asset hashes, collection version and selected source coverage. Download public model assets in a controlled preparation step; test runtime embedding without a network download. Use distinct query and indexing privileges where supported. Keep n8n on a narrow integration API rather than handing every workflow a database administrator key. Use a single writer and a stable source snapshot. Build into a staging collection and change the active alias only after verification. At query time, compare a semantic hit with the current screened source hash. Drop stale matches; never treat arbitrary payload text as authoritative. A partial semantic index should state indexed versus total chunks and characters. Keep exact text search available. Test that the intended product filter works, and that it is not mistaken for per user authorization. ### n8n acceptance Begin with a harmless internal health request. Next, import a controlled refresh workflow as the intended owner. Preserve the encryption key and inspect ownership after import. Use a fixed endpoint, an encrypted credential, bounded retries and a durable request identifier. Repeating the request should not duplicate the operation. Verify workflow activation in the running service; importing a file is not the same as activating a schedule. Test command line execution alongside the existing service. The observed setup encountered a task broker port collision, so CLI acceptance must include the actual running environment rather than only `--help` output. Do not enable arbitrary shell, SSH or unrestricted fetch nodes merely because an example workflow uses them. Reassess node permissions when adding collaborators. ## 8. Make CLI and remote job checks reproducible Download the probe into a private working directory after reading its source. It uses Python's standard library. Run it without sudo: ```bash python3 mobile-ai-workspace-probe.py --self-test python3 mobile-ai-workspace-probe.py --project /path/to/YOUR_PROJECT --output ./cli-check.json ``` The probe checks whether selected commands are present and return a version. It does not verify provider authorization, every subcommand, billing, or a production deployment. Keep the resulting JSON private: it includes your hostname and project path. Version manager pins in the project directory can cause a shim to fetch or install a toolchain. CLIs may also perform their own update or telemetry requests even though the helper does not implement network probes. A missing optional tool is recorded as missing. Add `--require git --require node` to make those particular checks necessary for a successful exit. After inventory, run the provider's native account status command from the environment that will perform the work. Review its output privately. Check the exact repository, site and account. Follow with a harmless project read, a preview where supported, and only then the approved production action. ### Detached job demonstration On a server with tmux installed, start a new session: ```bash tmux new-session -s workspace-proof ``` Inside it, run a harmless task and save its exit status: ```bash umask 077 python3 -c 'import time; time.sleep(30); print("proof complete")' > proof.log 2>&1; printf '%s\n' "$?" > proof.exit ``` Detach with Ctrl+B, then D. Reconnect with `tmux attach -t workspace-proof` and inspect the files. This proves a small process can survive a client detachment. It does not implement durable scheduling, single execution delivery or unattended deployment safety. For real jobs, keep a registry of approved tasks, a unique receipt, an atomic status record, a timeout and a log location. Reject unknown task names. If an operation was interrupted, inspect its last checkpoint before rerunning. Never infer successful work from the continued existence of a process. ### Prompt from a connected chat app > Use the enrolled remote machine named YOUR_HOST. Confirm the hostname and the project path before doing anything. Read the latest operation record. Run only the approved verification task for YOUR_PROJECT, save its result, and return the receipt and exit status. Do not execute on my phone, repeat a completed operation, print secrets, or deploy production without its separate approval. This requires an available connector with the necessary authorized actions. A text prompt alone does not grant access. Keep an independent recovery path if that connector or its provider is unavailable. ### Continue through Claude Code Confirm the example commands below against current documentation and your installed help output. Use the provider's supported Remote Control flow on the intended host and directory. Verify the native subscription login, working directory and permission mode before exposing the session through its authenticated account. ```bash claude auth status claude remote-control --help ``` Use the flags supported by your installed version. Keep normal approvals. Do not expose an unauthenticated shell or copy a browser token into a gateway to imitate this functionality. ## 9. Update through a tested candidate Use wildcard or latest selectors only to discover candidates from an explicit package allowlist. Resolve them to exact versions, save a dependency lock, build an immutable image and run the tests against that image. Do not make the production step pull a different floating tag after the test. Do not automatically force a dependency downgrade or an incompatible major version solely because an audit command suggests it. The release record should include: ```json { "release": "YOUR_RELEASE_ID", "source_commit": "YOUR_REVIEWED_GIT_COMMIT", "image_digest": "YOUR_TESTED_IMAGE_DIGEST", "previous_image_digest": "YOUR_PREVIOUS_DIGEST", "dependency_lock_hash": "YOUR_LOCK_HASH", "credential_source": "runtime_secret_store", "test_receipt": "YOUR_TEST_RESULT_LOCATION", "promotion_approved": false, "rollback_compatibility_reviewed": false, "production_verified": false } ``` A weekly GitHub Actions job should invoke a constrained update mechanism, not arbitrary root commands assembled from a workflow input. Keep the runner away from untrusted pull request code. Limit repository permissions and use a protected deployment environment where appropriate. Record discovery, build, test, promotion and rollback as distinct states. The mere presence of a scheduled YAML file is not proof that the runner is registered, available, authorized or capable of restoring the previous release. The source project had this weekly mechanism under implementation at the reporting checkpoint. This workbook does not claim it is a finished portable updater. ## 10. Retention and recovery are different controls Choose and document the local retention period. The source project's requested limit was 40 days for registered backup files. That is not a requirement for every reader and is not proof of offsite recovery. Register each backup with a path, kind, creation time and verified checksum. Delete only registered backup artifacts under an approved backup root. Refuse symlinks and unexpected paths. Coordinate cleanup with the backup writer. Never treat a live profile, original import or repository as an expired backup because its name contains backup or old. Test cleanup on synthetic files representing just inside and just outside the retention boundary. Include malformed dates, checksum mismatches, symlinks and a live data path. No real backup should be removed during these fixture tests. Separately restore the state into an isolated environment. Verify application data, credentials, workflow ownership and model/source indexes. Prove a recovery copy exists outside the machine you intend to retire. ## 11. Minimum release acceptance matrix | Test | Required observation | |---|---| | Fresh external login | Intended owner reaches the intended application | | Unauthenticated access | Private API and desktop are refused | | Retina stream startup | Bounded initial dimensions and actual rendered output | | Sustained session | Chosen test duration, connection count and decoder behavior recorded | | Keyboard and rotation | Draft survives and controls remain reachable | | Connection interruption | Clear nonlive state, bounded retries, no replayed input | | Expired authorization | Usable native approval route without losing task context | | Competing tab | Explicit takeover and revoked old input authority | | Clipboard and multiline paste | Correct characters, user gesture, no implicit Enter | | Upload/download | Verified bytes and destination; retry does not duplicate | | Missing source | Honest no evidence behavior | | Stale source | Old semantic hit cannot masquerade as current evidence | | Local model busy | Bounded wait or busy response, no silent paid fallback | | Workflow replay | Same identifier returns the same operation record | | Image replacement | Profile, data, secrets and owner permissions survive | | Rollback | Previous compatible service operates with reviewed state | | CLI account mismatch | Deployment stops instead of selecting a convenient account | | Build harness missing | The gate fails rather than reporting success | | Artifact publication | Remote Git commit and deployed files match the approved release | | Retention fixture | Only registered, expired test artifacts are eligible | Record exactly what was tested and what was not. A browser engine suite is not a physical device certification. A dependency scan is not a whole system security certificate. A healthy container is not an integrated business workflow. ## 12. Prompt for the next implementation > Read this workbook as an implementation contract. Begin by inventorying the existing environment and preserving its working access. Separate source, runtime state, credentials, and rebuildable indexes. Propose one small phase with explicit acceptance and rollback. Execute only the approved phase. Test successful, refused, interrupted and duplicate requests. Keep secrets out of images, logs and Git. Return the evidence, unresolved limitations, and the exact next decision. Never claim completion merely because the software installed or a page loaded. ## Documentation and source boundaries The operational lessons come from the dated companion evidence. The following vendor documentation describes supported interfaces; it does not certify this workbook or your deployment. Webtop setup and security: https://docs.linuxserver.io/images/docker-webtop/ Selkies controls: https://docs.linuxserver.io/selkies/user-guide/web-client/ WebKit clipboard behavior: https://webkit.org/blog/10855/async-clipboard-api/ Docker volumes and secrets: https://docs.docker.com/engine/storage/volumes/ and https://docs.docker.com/compose/how-tos/use-secrets/ Ollama context and concurrency: https://docs.ollama.com/faq LiteLLM route reliability: https://docs.litellm.ai/docs/proxy/reliability Qdrant security: https://qdrant.tech/documentation/operations/security/ n8n encryption and native CLI: https://docs.n8n.io/hosting/configuration/configuration-examples/encryption-key/ and https://docs.n8n.io/hosting/cli-commands/ Claude Code Remote Control: https://code.claude.com/docs/en/remote-control Connected apps in ChatGPT: https://help.openai.com/en/articles/11487775-connected-apps-in-chatgpt GitHub workflow security: https://docs.github.com/en/actions/reference/security/secure-use No passwords, API keys, private addresses or live session links are embedded in this workbook. Recheck provider behavior and plan terms before implementation.