T3 Code: Full-Remote Multi-Agent Development Workflows
/ 15 min read
Updated:Table des matières
Autonomous coding agents need a stable environment, fast compiler feedback, and carefully scoped access to resources. I have evolved my T3 Code environment: the custom image has given way to dizys/t3code-docker, the former secret modules have been consolidated into t3-vault, and agents route through private AI gateways.
Architecture overview
A remote architecture is more than a server reachable over a network. I separate the roles: browser and VS Code are clients, the Zero Trust boundary controls access, the container hosts T3 Code and its agents, and persistent storage survives image replacement.
The diagram shows functional flows without exposing hosts or infrastructure ports. Internal port references are intentionally generic (90xx). Arrows describe exchanges, not implicit authorization: each path should remain limited to its required service.
flowchart TB
subgraph Clients["Remote clients"]
Browser["Web browser"]
VSCode["VS Code Remote SSH"]
User["User"]
end
subgraph Edge["Zero Trust boundary"]
Access["Cloudflare Access and OIDC SSO"]
Tunnel["Tunnel to <service-host>"]
WARP["Private WARP mesh"]
end
subgraph Container["t3code container"]
SSHD["sshd, node account, Ed25519 keys"]
Server["t3 serve, web UI and workspace API"]
subgraph Agents["Agents"]
Codex["Codex CLI and codex-lsp"]
Pi["Pi Coding Agent 1.4.0"]
OpenCode["OpenCode and other agents"]
end
subgraph LSP["Shared LSPs"]
Lugo["Lugo, Lua 5.4 and FiveM"]
TS["typescript-language-server"]
Go["gopls"]
Ty["ty for Python"]
end
DockerClient["Read-only Docker client"]
end
subgraph Services["Internal services"]
Vault["t3-vault"]
Gateways["Private AI gateways"]
Proxy["docker-socket-proxy, POST=0"]
Model["Model providers"]
Stack["Stack services"]
end
subgraph Volumes["Persistent storage"]
Workspace["/workspace: repositories and worktrees"]
UserData["/data/userdata: preferences"]
end
User -->|"Controls session"| Browser
Browser -->|"Protected HTTPS"| Access
Access -->|"Forwards authorized session"| Tunnel
Tunnel -->|"Reaches web interface"| Server
User -->|"Opens IDE session"| VSCode
VSCode -->|"Private connection 90xx"| WARP
WARP -->|"Reaches SSH service"| SSHD
SSHD -->|"Opens remote workspace"| Workspace
Server -->|"Starts agents"| Codex
Server -->|"Starts agents"| Pi
Server -->|"Starts agents"| OpenCode
Codex -->|"Requests diagnostics"| LSP
Pi -->|"Requests diagnostics"| LSP
LSP -->|"Analyzes repository code"| Workspace
Codex -->|"Requests a secret from user"| Vault
Pi -->|"Requests a secret from user"| Vault
Codex -->|"Sends inference request"| Gateways
Pi -->|"Sends inference request"| Gateways
OpenCode -->|"Sends inference request"| Gateways
Gateways -->|"Relays within quotas"| Model
DockerClient -->|"Inspects through restricted API"| Proxy
Proxy -->|"Exposes allowed operations"| Stack
Server -->|"Persists preferences"| UserData
Server -->|"References active repositories"| Workspace
This separation helps reason about failures. Losing a container should not delete code or persistent preferences; a gateway outage should not prevent access to local worktrees; and access to the Docker proxy should not grant administrative capabilities.
1. Official container base and runtimes
Why replace the custom image
A locally modified image accumulates invisible differences: one dependency may be installed by hand, another built at a different version, and a rebuild may no longer reproduce the running container. I now use the published dizys/t3code-docker image, distributed through Docker Hub and GitHub Container Registry.
Choosing a published image moves the work rather than removing it. I maintain an explicit deployment configuration and separate the image, user data, and repositories. An image update is therefore not a backup mechanism, and a rollback does not depend on mutable container contents.
| Need | Location or mechanism | Trade-off |
|---|---|---|
| Application image | dizys/t3code-docker |
Follow a published artifact rather than a drifting local image |
| Repositories and worktrees | /workspace |
Working data stays separate from the image |
| User preferences | /data/userdata |
Persistent state stays separate from repositories |
| Additional tools | T3_PREINSTALL |
Declarative boot installation with startup cost |
Runtimes injected at startup
The runtime should support repository needs without turning the base image into a monolith. The T3_PREINSTALL variable injects Bun, uv, Wrangler, and Python 3 at startup, alongside Go, Git, ripgrep, and tools already available.
uv manages Python environments and dependencies; Wrangler serves Cloudflare projects. Installing these tools at boot makes their presence reproducible in configuration, but may extend startup and depend on package-source availability. I keep the list focused on tools actually needed by projects hosted in this environment.
# Illustrative container configuration excerpt.# Exact T3_PREINSTALL values are specific to the deployment.T3_PREINSTALL="bun uv wrangler python3"This notation describes the required tools and is not a universal installation command: the concrete syntax must match the published image’s documentation. Code and preferences remain in their persistent mounts, not in the installation process.
CLI tools for exploration and maintenance
Agents are more useful when they can inspect a codebase selectively instead of reading every file sequentially. I keep a CLI toolkit focused on search, syntax-aware transformation, and maintenance:
| Tool | Workflow use | Why keep it |
|---|---|---|
ripgrep (rg) |
Fast text search | Reduces noise during exploration |
fd |
File discovery | Complements text search with path matching |
fzf |
Interactive filtering | Speeds navigation through results |
ast-grep |
Syntax-aware search and rewriting | Avoids fragile textual replacements |
jq and yq |
JSON/YAML inspection and transformation | Avoids ad hoc scripts for configuration |
moreutils (sponge) |
Controlled file rewrites | Avoids in-place redirection pitfalls |
gh |
GitHub operations from CLI | Connects local work to pull requests and issues |
shellcheck and shfmt |
Shell analysis and formatting | Finds errors and style drift |
Having a tool available does not grant additional authorization. GitHub permissions, mounts, and network capabilities should remain narrowly scoped. A reproducible image and separated volumes make updates more predictable without confusing tool availability with privileges.
2. Unified Vault and secure secret management
One center for credentials
Fragmented modules require multiple access models and request behaviors. I replaced the former t3-ssh and t3-api modules with t3-vault, a unified center for credentials consumed by the workflow.
The Vault manages eleven credential families. Relevant examples include SSH keys, AWS and GCP cloud tokens, Kubernetes tokens, sensitive configuration files, and API keys. This range avoids treating every secret as a plain string, especially when an integration expects a file or a specific credential type.
| Representative family | Example use | Design precaution |
|---|---|---|
| SSH keys | Authentication to a repository or service | Never print the key in command output |
| AWS/GCP cloud tokens | Access to a cloud API | Limit permissions and duration |
| Kubernetes tokens | Operations on an authorized cluster | Restrict context and scope |
| Sensitive configuration files | Configuration consumed by a tool | Pass as a file, not conversation text |
| API keys | Calling a service | Centralize rotation and avoid per-agent copies |
These examples are not an exhaustive list of all eleven categories. They illustrate why a unified manager should preserve the secret’s type and consumption method.
Interactive request without conversation-history leakage
An agent should not ask a user to paste a password into the discussion thread. When it identifies a required credential, it asks T3 Code; the UI displays the native secret card, the user provides access, and the Vault mechanism gives the agent authorized access without injecting the value into conversation history.
sequenceDiagram
actor User as User
participant Agent as Autonomous agent
participant T3 as T3 Code interface
participant Vault as t3-vault
Agent->>T3: Request the required credential type
T3-->>User: Display native secret card
User->>T3: Approve and provide the secret
T3->>Vault: Resolve the requested credential
Vault-->>Agent: Provide access through the intended channel
Note over Agent,Vault: The secret value is not written to conversation history
This architecture reduces accidental copies, but does not make an agent trustworthy by default. I limit credentials to those needed by the task, review requested operations, and treat logs and tool outputs as surfaces to protect. The Vault separates a secret from conversation; it does not replace access control or action review.
3. Private AI gateways and quotas
Routing from Setup → Gateways
Connecting each agent directly to each provider multiplies keys, configuration, and billing paths. I configure Setup → Gateways to route Pi, OpenCode, and other agents to dedicated private gateways available on the internal Zero Trust network: CLIProxyAPI, LiteLLM, or OpenRouter.
The routing design separates agents from the final provider. An agent speaks to the gateway; the gateway applies the configured route and relays the request. This layer makes provider changes less invasive and enables centralized quota policy, but it also becomes an availability dependency and a boundary to monitor.
Agent (Pi / OpenCode / other) -> Private gateway selected in Setup → Gateways -> Configured model providerI intentionally publish no real hostname, endpoint, or port: the gateway is reachable only on the private network, and internal ports use the generic 90xx notation.
Keys and usage limits
Inference keys are kept in the Vault rather than copied into each agent configuration. Under Usage → Limits, I set strict quotas and limits per provider. This separation addresses two problems: reducing the number of credential copies and preventing an agent loop from consuming a provider’s budget without bounds.
| Layer | Responsibility | Expected control |
|---|---|---|
| Agent | Sends a compatible request | Explicitly selected model and task |
| Gateway | Routes and relays the request | Private network access and monitored configuration |
| Vault | Supplies required keys | No plaintext key in conversation history |
Usage → Limits |
Bounds consumption | Separate quotas per provider |
A quota can interrupt a long task; that is a preferable trade-off to unbounded consumption. I therefore set limits appropriate to the task and check gateway status before attributing a failure to the model or code.
4. Dual remote access and Zero Trust hardening
Web UI and VS Code session
A browser is convenient for checking a task and controlling a session without preparing a local client. For longer editing sessions, VS Code Remote SSH provides a local IDE connected to the remote environment. I keep both paths and their native reconciliation so that they reach the same workspace rather than divergent states.
The web interface is protected by Cloudflare Access and OIDC SSO. The VS Code Remote SSH session reaches the container over the private WARP mesh. No precise infrastructure port is published here; 90xx is the generic notation for internal ports only.
flowchart LR
User["Remote developer"] -->|"Opens the interface"| Browser["Modern browser"]
Browser -->|"OIDC SSO authentication"| Access["Cloudflare Access"]
Access -->|"Authorized session"| Web["T3 Code Web UI"]
User -->|"Opens VS Code Remote SSH"| VSCode["VS Code client"]
VSCode -->|"Private connection 90xx"| WARP["WARP mesh"]
WARP -->|"Authenticates with Ed25519 key"| SSH["Container sshd, node user"]
Web -->|"Reconciles the session"| Workspace["T3 Code workspace"]
SSH -->|"Opens the same workspace"| Workspace
SSH uses only the unprivileged node user, authenticated with Ed25519 keys. The private network reduces exposure, while key authentication avoids a reusable password. These controls do not remove the need to protect client keys and revoke those no longer required.
Setup, cookies, and proxy headers
The setup protocol is a trust boundary: the setup-key is used for setup and must not be inherited by agent subprocesses. Preventing inheritance reduces the risk that an agent-launched process can read the key from its environment.
Cookies are protected to avoid trivial session reuse. When T3 Code sits behind a trusted proxy, T3_TRUST_PROXY_HEADERS=1 enables handling of proxy headers. This option is safe only if the upstream proxy strips or replaces client-supplied headers and direct access to the service is controlled. Without these conditions, a client could forge transport or origin information the application considers trusted.
| Control | Risk addressed | Operational dependency |
|---|---|---|
Do not inherit setup-key |
Subprocess access to the key | Inspect agent process environment |
| Protected cookies | Session replay or exposure | Correct server and browser configuration |
T3_TRUST_PROXY_HEADERS=1 |
Incorrect interpretation of proxy data | Trusted upstream proxy and closed direct path |
| WARP and Ed25519 key | Unauthorized SSH access | Enrolled-device and key management |
5. Multi-agent engine: Codex and Pi
Agents do not all serve the same role. A single profile can handle both a local fix and architectural exploration, but that forces a trade-off between speed, planning, and delegation. I keep Codex CLI and Pi Coding Agent side by side and assign them according to the shape of the work.
| Criterion | Codex CLI | Pi Coding Agent 1.4.0 |
|---|---|---|
| Preferred use | Focused refactors, atomic fixes, tests | Architecture exploration and multi-step tasks |
| Feedback loop | codex-lsp relays LSP diagnostics |
Can delegate analysis to subagents |
| Parallelism | Focused task in a dedicated worktree | Background Explore and Plan subagents |
| Main caution | Validate changes and diagnostics | Coordinate results and control scope |
Codex CLI and codex-lsp
For a bounded change, Codex CLI can inspect code, modify a focused area, and run relevant checks. The codex-lsp plugin provides live diagnostics from language servers. The model can respond to type or syntax errors while editing instead of discovering every issue after a long test phase.
This loop does not replace tests. An LSP diagnostic proves neither functional behavior nor migration safety; it only reduces the delay before detecting some static problems.
Pi and Explore / Plan subagents
For a cross-cutting question, Pi Coding Agent 1.4.0 can start Explore and Plan subagents in the background. One can map entry points while another examines constraints or proposes a sequence of changes. The main thread retains synthesis and decides what to modify.
Delegation increases exploration throughput but also increases the volume of results to verify. I request bounded analysis tasks, avoid having multiple agents write to the same worktree, and reconcile proposals before implementation. Each agent uses configured gateways and receives only the secrets explicitly requested.
6. Shared language servers
An agent without a language server may invent a signature, miss an import, or discover an error only at build time. To reduce that delay, Codex and Pi share LSPs installed under /home/node/.local/opt/t3code-lsp.
| Language | Server | Role and details |
|---|---|---|
| Lua 5.4 / FiveM | Lugo | Go language server with fxmanifest.lua awareness, network-event arity analysis, and trust-boundary checks |
| TypeScript / JavaScript | typescript-language-server |
Navigation and diagnostics for TypeScript/JavaScript alongside workspace runtimes |
| Go | gopls |
Diagnostics, navigation, and type information for Go projects |
| Python | ty |
Python static analysis and type checking |
Lugo is particularly useful for FiveM projects: the manifest identifies relevant resources and files, while network-event analysis helps find mismatches between calls and handlers. Trust-boundary diagnostics provide an additional signal, but are not a complete security audit.
The shared directory reduces binary duplication and keeps both agents on consistent tools. In return, a language-server update can affect every consumer, so I control versions and check diagnostics after changes. LSP and build tools have different goals; tests and the compiler remain necessary.
/home/node/.local/opt/t3code-lsp/├── lugo├── typescript-language-server├── gopls└── ty7. The golden rule: absolute Git worktree isolation
Why direct work on develop is banned
A shared branch is one mutable workspace. If an agent changes files while I test, it can overwrite ongoing work, change the index, or create a state that is difficult to attribute. Editing develop directly also exposes the reference branch to incomplete changes.
I therefore ban agent edits directly on develop. Each task runs in a distinct worktree created from the intended remote reference. A worktree shares the Git database but has its own directory and working state. This is lighter than an independent clone while isolating uncommitted changes.
workspaceStrategy: { type: "worktree", baseRef: "origin/develop"}Lifecycle of an isolated task
The lifecycle starts by updating the remote reference, creates a dedicated worktree, assigns its path to an agent, and checks the result before publication. Once the task is complete, the temporary worktree is removed; the pushed branch retains commits for review.
sequenceDiagram
autonumber
actor Dev as Developer
participant T3 as T3 Code
participant Git as Repository under /workspace
participant Agent as Codex or Pi
Dev->>T3: Create a task in a worktree
T3->>Git: git fetch origin develop
T3->>Git: git worktree add ../wt-task origin/develop
Note over Git: Dedicated directory, shared Git database, separate changes
T3->>Agent: Assign the worktree path
loop Implementation and verification
Agent->>Agent: Edit, consult LSP, and run relevant tests
end
Agent->>Git: Create a commit on the task branch
Agent->>Git: Push the branch for review
T3->>Git: git worktree remove ../wt-task
T3-->>Dev: Worktree removed, branch available for the PR
What isolation guarantees—and what it does not
| Benefit | Mechanism | Limit to remember |
|---|---|---|
| Parallelism | One directory per task | Two tasks can still change the same logic on separate branches |
| Clean baseline | Start from updated origin/develop |
Fetch the correct reference before starting |
| No pollution of active tree | Agent does not edit the developer’s active workspace | Changes still need review and integration |
| Cleanup | Remove the worktree after publication | Uncommitted changes require an explicit decision |
A worktree does not resolve conceptual conflicts: two agents can propose incompatible implementations. It only makes changes isolatable, comparable, and reversible. Diff review and tests remain mandatory before merging.
8. Secure Docker diagnostics for the VPS stack
An agent may need to inspect service state for diagnostics without receiving administrative privileges over the Docker daemon. I use docker-socket-proxy, configured for read-only access with POST=0, to limit the exposed operations.
T3 Code -> Docker client -> docker-socket-proxy (POST=0) -> stack servicesThe proxy reduces the attack surface; it is not a magical boundary. I do not mount the raw Docker socket into agents and limit available operations to those needed for diagnostics. Even a read-only API can reveal operational metadata; responses should be treated as internal information and not copied into public output.
This lets an agent help inspect service state while separating observation from administration. It does not replace centralized logs or an incident-management process, and it does not authorize changes to the stack.
Conclusion and open-source components
This evolution preserves the depth of the remote workflow while adding new security and operational boundaries. dizys/t3code-docker provides the published image; /workspace and /data/userdata separate repositories and preferences; T3_PREINSTALL adds runtimes; t3-vault centralizes credentials; and private gateways provide routing and limits.
Codex CLI and Pi address different tasks but share the four LSPs and work in isolated worktrees. The protected browser and VS Code Remote SSH offer complementary access, while setup controls, cookies, the trusted proxy, and Docker proxy limit exposure.
I prefer explicit boundaries over a promise of risk-free autonomy: each agent receives a workspace, gateway, and the secrets required for its task, then its changes are reviewed before integration. The published image and open-source components can be identified by name, including dizys/t3code-docker; their public repositories are the starting point for reviewing source code and documentation.