vev agent guide
Project
vev is a Linux terminal multiplexer written in Go. It has a per-user daemon, a thin client, server-side rendering, and minimal socket/SSH diffs.
Commands
go build -o vev .
make test
go test ./internal/usecase/daemon -race -run TestName
make lint
make mocks
go test ./pkg/renderer ./pkg/vt ./internal/adapters/ipc ./internal/usecase/daemon -run '^$' -bench=. -benchmem
- Format with goimports.
- Install dev tools once with
go install golang.org/x/tools/cmd/goimports@latest and go install github.com/vektra/mockery/v3@v3.7.1.
- Use Mockery v3 consistently across Go projects.
make test runs go test ./... -race.
make lint checks goimports -l, then go vet.
- Enable daemon pprof with
VEV_PPROF_ADDR=127.0.0.1:6060 vev --daemon.
- Set
VEV_LOG=debug|warn|error to change log verbosity (default info).
- Logs live in
$XDG_STATE_HOME/vev, or ~/.local/state/vev when unset.
- Use feature worktrees under
.worktrees/<branch-name>. Remove the worktree once its branch is merged or abandoned (git worktree remove <path>).
- Create vev-owned private directories with
pkg/safedir.EnsurePrivate, not os.MkdirAll.
Architecture rules
Hexagonal boundaries are enforced by boundary_test.go.
pkg/ is reusable and must not import internal/.
internal/usecase/ must not import internal/adapters/.
- Usecases depend on
internal/ports and internal/domain.
Layer map:
main.go → internal/app: CLI parsing, wiring, daemon startup, hidden subcommands.
internal/ports: interfaces and wire protocol.
internal/usecase/daemon: sessions, tabs, panes, VT screens, renderer shadows, daemon features.
internal/usecase/client: raw-mode thin client; writes output bytes verbatim and interprets nothing.
internal/adapters: IPC, SSH stdio, PTY, terminal, and clock implementations.
Before touching daemon teardown paths, read the lock-ordering notes at the top of internal/usecase/daemon/client.go.
Ports and mocks
- Define cross-layer interfaces only in
internal/ports.
- Adapters implement ports; usecases and
internal/app consume ports.
- Tests should prefer generated
portsmocks.MockX with .EXPECT().
- Hand-written fakes are okay only when mocks make the test harder to understand.
- After changing a port, run
make mocks and commit the generated updates.
- Time-dependent daemon code must use
ports.Clock/ports.Timer, not direct wall-clock APIs.
Tests
- Write Go tests as table-driven tests by default.
- Keep wire tests byte-for-byte and include truncated-prefix and trailing-garbage coverage.
- Prefer fake clocks over sleeps.
Wire protocol
Wire payload types/codecs live in internal/ports/frame.go and internal/ports/wire.go. Connection framing lives in internal/adapters/ipc/transport.go.
- IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.
- Client message types occupy
1–13 (MsgOutputResetRequest is 13) and 15 (MsgRemotePreviewRequest); server types occupy 16–23 (MsgNavigationAction is 23) and 25–30 (MsgAttachTarget, MsgRemotePreviewResponse, MsgCommittedRouteIdentity, MsgRecentRouteSnapshot, MsgNavigateRecentRoute, and MsgRouteNavigationFailure). Types 14 and 24 remain reserved.
- Version negotiation requires strict equality.
Hello.Version and CommandRequest.Version must stay first so their version peekers work.
- Bump
ProtocolVersion for any message layout change.
Session flow
- Ephemeral numbered sessions survive detach while the daemon retains them, but are not persisted.
- Named sessions survive headless and persist across daemon restarts.
- The daemon starts on first use and exits when the last session ends.
- Each connection has a 15-second handshake budget from connect through the initial committed publication.
- Local and remote attach use the same
Hello/Welcome session protocol, then pump Input/Resize and Output frames over a byte-only Transport.
- Remote attach opens a direct connection to the selected daemon over UDP by default;
VEV_REMOTE_TRANSPORT=stdio explicitly selects an SSH-only carriage.
- A session owns shared PTYs, VT state, tabs, panes, PTY content geometry selected by the latest valid attachment claim, and ordered mutations. Each attachment owns its window/view, copy and overlay state, rendering/output state, and reconnect lifecycle; when the latest claimant detaches, the most recently claimed remaining attachment becomes authoritative.
- Command requests have a 10-second result deadline and are tracked per connection.
source
1# vev agent guide
2
3## Project
4
5vev is a Linux terminal multiplexer written in Go. It has a per-user daemon, a thin client, server-side rendering, and minimal socket/SSH diffs.
6
7## Commands
8
9```sh
10go build -o vev .
11make test
12go test ./internal/usecase/daemon -race -run TestName
13make lint
14make mocks
15go test ./pkg/renderer ./pkg/vt ./internal/adapters/ipc ./internal/usecase/daemon -run '^$' -bench=. -benchmem
16```
17
18- Format with **goimports**.
19- Install dev tools once with `go install golang.org/x/tools/cmd/goimports@latest` and `go install github.com/vektra/mockery/v3@v3.7.1`.
20- Use Mockery v3 consistently across Go projects.
21- `make test` runs `go test ./... -race`.
22- `make lint` checks `goimports -l`, then `go vet`.
23- Enable daemon pprof with `VEV_PPROF_ADDR=127.0.0.1:6060 vev --daemon`.
24- Set `VEV_LOG=debug|warn|error` to change log verbosity (default info).
25- Logs live in `$XDG_STATE_HOME/vev`, or `~/.local/state/vev` when unset.
26- Use feature worktrees under `.worktrees/<branch-name>`. Remove the worktree once its branch is merged or abandoned (`git worktree remove <path>`).
27- Create vev-owned private directories with `pkg/safedir.EnsurePrivate`, not `os.MkdirAll`.
28
29## Architecture rules
30
31Hexagonal boundaries are enforced by `boundary_test.go`.
32
33- `pkg/` is reusable and must not import `internal/`.
34- `internal/usecase/` must not import `internal/adapters/`.
35- Usecases depend on `internal/ports` and `internal/domain`.
36
37Layer map:
38
39- `main.go` → `internal/app`: CLI parsing, wiring, daemon startup, hidden subcommands.
40- `internal/ports`: interfaces and wire protocol.
41- `internal/usecase/daemon`: sessions, tabs, panes, VT screens, renderer shadows, daemon features.
42- `internal/usecase/client`: raw-mode thin client; writes output bytes verbatim and interprets nothing.
43- `internal/adapters`: IPC, SSH stdio, PTY, terminal, and clock implementations.
44
45Before touching daemon teardown paths, read the lock-ordering notes at the top of `internal/usecase/daemon/client.go`.
46
47## Ports and mocks
48
49- Define cross-layer interfaces only in `internal/ports`.
50- Adapters implement ports; usecases and `internal/app` consume ports.
51- Tests should prefer generated `portsmocks.MockX` with `.EXPECT()`.
52- Hand-written fakes are okay only when mocks make the test harder to understand.
53- After changing a port, run `make mocks` and commit the generated updates.
54- Time-dependent daemon code must use `ports.Clock`/`ports.Timer`, not direct wall-clock APIs.
55
56## Tests
57
58- Write Go tests as table-driven tests by default.
59- Keep wire tests byte-for-byte and include truncated-prefix and trailing-garbage coverage.
60- Prefer fake clocks over sleeps.
61
62## Wire protocol
63
64Wire payload types/codecs live in `internal/ports/frame.go` and `internal/ports/wire.go`. Connection framing lives in `internal/adapters/ipc/transport.go`.
65
66- IPC frames on a connection are 4-byte big-endian length, 1 type byte, then payload.
67- Client message types occupy `1–13` (`MsgOutputResetRequest` is `13`) and `15` (`MsgRemotePreviewRequest`); server types occupy `16–23` (`MsgNavigationAction` is `23`) and `25–30` (`MsgAttachTarget`, `MsgRemotePreviewResponse`, `MsgCommittedRouteIdentity`, `MsgRecentRouteSnapshot`, `MsgNavigateRecentRoute`, and `MsgRouteNavigationFailure`). Types `14` and `24` remain reserved.
68- Version negotiation requires strict equality.
69- `Hello.Version` and `CommandRequest.Version` must stay first so their version peekers work.
70- Bump `ProtocolVersion` for any message layout change.
71
72## Session flow
73
74- Ephemeral numbered sessions survive detach while the daemon retains them, but are not persisted.
75- Named sessions survive headless and persist across daemon restarts.
76- The daemon starts on first use and exits when the last session ends.
77- Each connection has a 15-second handshake budget from connect through the initial committed publication.
78- Local and remote attach use the same `Hello`/`Welcome` session protocol, then pump `Input`/`Resize` and `Output` frames over a byte-only Transport.
79- Remote attach opens a direct connection to the selected daemon over UDP by default; `VEV_REMOTE_TRANSPORT=stdio` explicitly selects an SSH-only carriage.
80- A session owns shared PTYs, VT state, tabs, panes, PTY content geometry selected by the latest valid attachment claim, and ordered mutations. Each attachment owns its window/view, copy and overlay state, rendering/output state, and reconnect lifecycle; when the latest claimant detaches, the most recently claimed remaining attachment becomes authoritative.
81- Command requests have a 10-second result deadline and are tracked per connection.