aller au contenu
FRFlo
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.

Terminal window
# 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 provider

I 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
└── ty

7. 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 services

The 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.

Drag to move, use the wheel or pinch to zoom. Keyboard: arrow keys, + and −.