Agent Guide
Agent Guide
Rust + Godot multi PTY emulator with a tiling grid GUI.
Guides
This file holds the rules every change needs. The detailed rules live in one page per topic; read the ones your change touches before editing.
| Guide | Read it when |
|---|---|
| Testing | Writing or running tests, the gate commands, the UI/UX assessment method |
| Rust conventions | Touching anything under crates/ |
| GDScript conventions | Touching anything under godot/ |
| Concept capture | Concepts, triggers, stop conditions, routing |
| Inspector, Reasoning, and OMP | The agent-observability panes |
| Plugin manifest | Plugin manifests, catalog, install and trust |
| Security rules | Any change near IPC, PTY, the filesystem, plugins, networking, or secrets (read before editing) |
| Pitfalls and tool notes | Before debugging, and before touching an FFI or CLI boundary: the known traps |
Project Structure
- Stack: Rust (edition 2024) backend + Godot (4.7) with a GDScript frontend via
gdext(0.5) - Entry point:
godot/scenes/main.tscn→workspace.gd(rootControlnode)
gpty/
├── Cargo.toml # Workspace root
├── AGENTS.md # Agent guide core; topic pages live in docs/content/docs/agents/
├── ROADMAP.md # Opening text + links to the release-chain pages
├── CHANGELOG.md # Opening text + links to the per-release pages
├── docs/
│ ├── content/docs/agents/ # Agent guide topic pages (Rust, GDScript, security, ...)
│ ├── content/docs/roadmap/ # One page per release chain (v0.N.md, future.md)
│ ├── content/docs/changelog/ # One page per release (vX.Y.Z.md)
│ ├── content/wiki/ # Knowledge-base vault; scripts/build-wiki packs it into godot/knowledge.json
│ └── static/images/ # Release screenshots + roadmap evidence images
├── LICENSE
├── LICENSE-EXCEPTIONS.md # GPLv3 §7 permissions: plugins + data stay non-copyleft
├── scripts/ # CI runner and setup scripts
│ ├── ci-check # Run all CI checks locally (--fast for quick)
│ ├── build-wiki # Pack docs/content/wiki into godot/knowledge.json
│ ├── install-hooks # Symlink githooks into .git/hooks/
│ └── build # Build standalone app for the host platform
├── githooks/ # Git hook scripts
│ ├── pre-commit # Fast checks (fmt, workflow lint, clippy)
│ ├── pre-push # Full CI suite before push
│ └── commit-msg # Conventional Commits enforcement
├── crates/
│ ├── gpty-ai/ # Inspector backends (mock, private OMP RPC, CLI NDJSON bridge; no tools)
│ ├── gpty-core/ # PTY spawning, ANSI parsing, alacritty_terminal grid, capture
│ │ └── src/
│ │ ├── lib.rs # Module map + data-flow diagram
│ │ ├── types.rs # Concept, Action, CaptureMode, CapturedOutput
│ │ ├── concept.rs # Trigger matching + capture routing (pure fns)
│ │ ├── engine.rs # WorkspaceEngine, capture state machine, SpawnedTerminal
│ │ ├── pty.rs # portable-pty spawn + dedicated I/O thread
│ │ ├── parser.rs # vte → plain-text LineParser
│ │ ├── term.rs # alacritty_terminal grid + CellInfo + damage tracking
│ │ ├── color.rs # ANSI color → RGB
│ │ ├── keymap.rs # Key event → byte sequence
│ │ ├── cli.rs # CliProcess: piped argv child, line queue
│ │ ├── history.rs # SQLite scrollback store
│ │ └── vault.rs # SQLite + FTS5 vault index (wiki pane's notes and search)
│ ├── gpty-media/ # Media backend: pinned FFmpeg binding, sessions, packed frames/PCM
│ │ ├── components.txt # Closed FFmpeg component set (build recipe ↔ runtime allowlists)
│ │ ├── src/
│ │ │ ├── lib.rs # Path gate, caps, allowlists, module map
│ │ │ ├── probe.rs # Open/inspect, demuxer aliases, stream selection
│ │ │ ├── session.rs # Decode thread, bounded queues, seek/epoch, RGBA/PCM
│ │ │ └── error.rs # Stable error codes
│ │ └── tests/ # Fixtures (generated), decode/seek/path tests, binding rules
│ ├── gpty-cli/ # CLI workspace control over JSON-RPC IPC
│ │ ├── Cargo.toml
│ │ ├── skills/gpty/SKILL.md # Bundled agent skill, printed by `gpty --skill`
│ │ └── src/
│ │ ├── main.rs # clap CLI entry point
│ │ └── commands/ # Subcommand handlers
│ │ ├── mod.rs
│ │ ├── new_pane.rs
│ │ ├── list_panes.rs
│ │ ├── kill_pane.rs
│ │ ├── focus_pane.rs
│ │ ├── inject.rs
│ │ ├── pane_read.rs
│ │ ├── pane_status.rs
│ │ ├── pane_run.rs
│ │ ├── pane_wait.rs
│ │ ├── broadcast.rs
│ │ ├── schema.rs
│ │ ├── mcp.rs
│ │ ├── concept.rs
│ │ ├── daemon.rs
│ │ └── layout.rs
│ ├── gpty-ipc/ # Shared JSON-RPC 2.0 IPC transport, client, and server
│ │ └── src/
│ │ ├── protocol.rs # JSON-RPC 2.0 Request, Response, JsonRpcError
│ │ ├── types.rs # IPC domain types (NewPaneParams, PaneInfo, …)
│ │ ├── transport.rs # Platform socket connection (Unix, named pipe)
│ │ ├── server.rs # Async IPC server with handler registry
│ │ └── client.rs # Async IPC client with connect/call/timeout
│ └── gpty-gdext/ # GDExtension cdylib: GptyTerminal + GptyAi + GptyCliView + GptyMarkdown
│ └── src/
│ ├── lib.rs # GptyTerminal GodotClass: session lifecycle, input, grid access helpers
│ ├── grid_api.rs # GptyTerminal grid facet: packed updates, cursor, scrolling, live search
│ ├── history_api.rs # GptyTerminal history facet: flush, prune, FTS search, size on disk
│ ├── vault_api.rs # GptyTerminal vault facet: note index, vault search (static)
│ ├── capture_api.rs # GptyTerminal capture facet: engine-global concepts, per-pane captures
│ ├── bridge_api.rs # GptyTerminal bridge facet: IPC queue, event fan-out, static metadata
│ ├── ai.rs # Per-pane GptyAi session bridge
│ ├── ipc.rs # Workspace-control IPC server
│ ├── omp_events.rs # Capability-scoped OMP event socket
│ ├── cli_view.rs # GptyCliView: argv child, streamed stdout
│ ├── markdown.rs # Markdown → sanitized BBCode
│ └── media_api.rs # GptyMedia: JSON + packed arrays over gpty-media
├── extensions/
│ └── gpty-omp-events/ # Explicit OMP plugin; dormant unless GPTY_EVENT_* is set
└── godot/ # Godot 4.7 project
├── project.godot
├── gpty.gdextension
├── concepts.default.json # Shipped default concepts
├── profiles.default.json # Shipped recommended layouts (Agent Workspace, Knowledge Base)
├── knowledge.json # Generated wiki bundle (scripts/build-wiki)
├── fonts/ # DejaVu Sans Mono + Phosphor icons
└── scenes/
├── main.tscn
├── autoloads/ # Singleton managers
│ ├── base_persistence_manager.gd
│ ├── settings_manager.gd
│ ├── profile_manager.gd
│ ├── concept_manager.gd
│ ├── workspace_store.gd # Named workspace (tab set) persistence
│ ├── pane_env_store.gd # User-owned per-pane env, keyed by attachment_id
│ ├── trusted_store.gd # Workspace Trust consent memory (builtins + plugins)
│ ├── link_weights_store.gd # User-owned declared link weights (display-only)
│ ├── focus_manager.gd
│ ├── toast_manager.gd
│ ├── shortcut_manager.gd
│ └── update_checker.gd
├── terminal/
│ ├── workspace.gd # Root controller, workspace switching, concept routing, profile restore
│ ├── ipc_handlers.gd # WorkspaceIpcHandlers - pane-API/IPC method dispatch (extracted)
│ ├── concept_router.gd # Pure concept-event routing (extracted, testable)
│ ├── plugin_review_text.gd # Pure plugin-review dialog text (extracted, testable)
│ ├── terminal_pane.gd # Control-based renderer, keyboard, mouse, selection, history search
│ ├── terminal_manager.gd # Tile lifecycle, split/kill/swap/spawn
│ └── tile_drag.gd # Divider-drag state machine (extracted)
├── ui/
│ ├── sidebar.gd
│ ├── settings_panel.gd
│ ├── status_bar.gd
│ ├── toast_overlay.gd
│ ├── window_chrome.gd # Titlebar + window-mode/fullscreen handling (extracted)
│ ├── markdown_view.gd # Safe, debounced Markdown RichTextLabel
│ ├── markdown_anchors.gd # Pure ATX heading scan for fragment/line navigation
│ ├── vault_graph_model.gd # Pure vault link-graph normalize/layout/hit-test
│ ├── link_weights.gd # Pure declared-weight vocabulary (paths, range, lookup)
│ ├── vault_graph_view.gd # Custom-drawn vault link-graph canvas
│ ├── palette.gd # Shared UI accent colors
│ └── icons.gd # Phosphor icon constants
└── panes/
├── pane_body.gd # Base class + routed-content contract
├── pane_types.gd # Pane registry, sanitizers, palette command list
├── viewable_files.gd # Closed table: which paths open in the viewer
├── media_files.gd # Closed table + path gate for the media pane
├── code_viewer.gd
├── file_tree.gd
├── text_read.gd # Shared read guard: regular-file gate + 1 MiB cap
├── inspector_pane.gd # Private read-only OMP Q&A
├── cli_view_pane.gd # Command stdout streamed into a pane body
├── reasoning_pane.gd # Passive OMP reasoning projection
├── media_pane.gd # Local audio/video playback
└── wiki_pane.gd # Vault browser and Markdown note editorCommands
See Contributing for all development, build, test, and CLI commands.
Data Flow
Shell → PTY I/O thread → vte parser → alacritty_terminal grid → Arc<Mutex<TermGrid>> → GptyTerminal (gdext) → GDScript _draw()CLI → Unix socket (gpty.sock) → IpcServer (tokio) → PENDING_IPC queue → GDScript _poll_ipc_requests() → workspace methods → IPC_RESPONDERS → CLI responseOMP TUI (user-launched in a terminal) → @gpty/omp-events extension → gpty-events.sock (ompEvent) → omp_events.rs translates to the generic vocabulary (session.bound, agent.started, turn.started, tool.call, thinking.delta, …) → GptyTerminal.drain_agent_events() → Reasoning pane + Tier 1 agent-state badges
Any pane program → `gpty state <value>` → gpty-events.sock (ompEvent, the pane's own capability) → `state.declared` → Tier 1 agent-state badge (the declaration path on Windows, where ConPTY consumes the `gpty_state` OSC)Inspector prompt → GptyAi.session_open/session_prompt → private omp --mode rpc --no-tools --no-session process → session_poll envelopescli_view pane config (command + shell_args) → GptyCliView.start(argv) → CliProcess (piped child, merged stdout/stderr) → line queue → GptyCliView.poll_lines → CliViewPane bodymedia pane → GptyMedia.open(JSON) → gpty-media MediaSession worker thread (pinned FFmpeg demux/decode) → bounded RGBA/PCM queues → GptyMedia.poll_video/poll_audio → paneMCP Integration
gPTY ships an MCP server (gpty mcp) that exposes 22 tools for AI agent integration.
AI agents and coding harnesses can discover these via the mcp.json that’s at the root of the repository.
// mcp.json - Coding harnesses should discover this automatically
{"mcpServers": {"gpty": {"command": "gpty", "args": ["mcp"]}}}Tools: new-pane, list-panes, kill-pane, focus-pane, inject, layout-save, layout-load, layout-list, daemon-start, daemon-stop, daemon-status, concept-list, concept-toggle, pane-read, pane-status, pane-run, pane-wait, broadcast, vault-search, vault-read, vault-weight, version.
tools/call validates the requested name against schema::mcp_tool_names() before dispatch: the kebab→camel mapping turns any string into an IPC method, so without the gate a client reaches methods that were never published as tools (e.g. shutdown).
The MCP tool schemas are auto-generated from clap command definitions in crates/gpty-cli/src/commands/schema.rs. Nested subcommands (daemon, layout) are flattened into prefixed tools. Self-referential tools (mcp, schema) are excluded.
See skill://gpty-omp-integration for usage patterns.
Roadmap workflow
ROADMAP.md is an index, and the roadmap itself is a prioritized queue split into one page per release chain under docs/content/docs/roadmap/ (v0.1.md … v1.0.md, then future.md; the Hugo site renders them as children of the Roadmap section, and docs/content/docs/roadmap/_index.md renders ROADMAP.md as the section’s front page). Work it like this:
- Picking the next task - the next task is the first unchecked row (⬜) in the earliest unfinished release: open the chain pages in version order, take the first chain that still has a ⬜, and take the first ⬜ on that page (a chain page lists its releases oldest-first, so an earlier release with a leftover ⬜ comes before a later one). The index marks each chain
released,in progress,plannedorunscheduled. Future is never picked from. No grouping by origin, no separate “carry-over” headings: within a release, row order is the priority, and the top row is the answer to “what’s next”. - Row format - one row per line in the page’s table:
| ✅ or ⬜ | AREA | **Title** - evidence, fix, tests |. The Area is one of TERM, GUI, IPC, CLI, SEC, PLUGIN, WIKI, CONCEPT, MEDIA, AGENT, CORE, BUILD, DOCS, PROJECT. Escape a literal|in the text as\|, and keep<inside backticks (the site omits raw HTML). There is no size or discovered-date column: sizing was only a tie-break, and provenance is in git history and, where it matters, in the row text (“found by the October audit”, “measured on commit …”). Titles stay unique; other rows and docs refer to a row by its bold title. - Ordering (used when adding or re-prioritizing rows) - data-loss and correctness first, then security with a reachable path, then the release’s own features, then robustness and tests, then low-reachability hardening, then polish, then work that can only happen at release time (tagging, packaging against a released artifact). Ties break toward the smaller item. The owner may promote the release’s headline feature ahead of the hardening tail once the correctness and security block is closed; when it is done, say so in the row rather than leaving the queue ambiguous.
- Future - ideas that are not designed yet and work that says in its own text what evidence would start it. Anything concrete and committed belongs in a numbered release (new releases may be added beyond the current last one, and v1.0 stays last); when a Future entry is defined enough to build, move it into a release in priority position.
- New rows found while working - a defect or follow-up discovered mid-task does not get fixed silently, queued in a comment, or folded into an unrelated commit: add it to the right release as its own row, in priority position, with the evidence that makes it actionable (what breaks, how it was found, what the fix is). Fixing it immediately is fine when it is genuinely part of the current row’s contract; otherwise it waits in the queue.
- Evidence screenshots -
docs/static/images/holds two kinds of image, told apart by name. Release screenshots arev<version>_<n>.png(written byscripts/screenshot-gpty, used by the release blog entry and README). Evidence for a roadmap row isissue-v<build>-<slug>.png:<build>is the version the capture was taken on (an issue image is only true for that build, so a recapture adds a new file instead of replacing one),<slug>names what it shows. Never embed an image in a table cell. List the images in a## Screenshotssection at the bottom of the chain page, each as a bold caption that names the row(s) it evidences by their bold titles and says what every numbered box marks (the numbers live in the image and nothing else explains them), then the image as; the row itself can end with a short pointer such as “Screenshot (under Screenshots below): Pane Settings.” Audits, assessments and design reviews are not committed as documents: their findings and outcomes become roadmap rows, the method goes in AGENTS.md, and only the captures a row points at are kept. - Completed rows stay under their release with the evidence (what shipped, measured numbers, tests) - the release section doubles as the record. The changelog carries the user-facing summary at release time: one page per release under
docs/content/docs/changelog/(vX.Y.Z.md), linked from the rootCHANGELOG.md, which holds only its opening text and the link list. - Folded-up detail - when a triage note, correction, or clarification exists for a row, it belongs in that row’s text, not in a separate “corrections” row. A row that turns out to be partly done gets narrowed to what remains rather than left asserting a fixed problem.
- Adding a chain - create
docs/content/docs/roadmap/<slug>.mdwith front matter (title, aweightafter the previous chain’s), a short paragraph on what the chain accomplishes, then one## vX.Y.Z - Namesection per release with the table; link it fromROADMAP.mdasdocs/content/docs/roadmap/<slug>.md(the site rewrites that to a section-relative link).
ADE Architecture Boundary
gPTY is evolving from a multi-terminal emulator into an ADE - a graphical PTY foundation with a public, agent-facing API:
| Layer | Owner | Content |
|---|---|---|
| L4 | ecosystem | Agent CLIs & apps: claude code, codex, opencode, OMP, vim, btop |
| L3 | ecosystem (open market) | Orchestration: agent waits, pane states, plugin workflows (herdr-class) |
| L2 | gPTY | Control surface: JSON-RPC socket, CLI, MCP, event socket |
| L1 | gPTY | PTY foundation: PTY lifecycle, grid, capture, history, security |
| L0 | OS | /dev/ptmx, ConPTY, sockets |
- gPTY owns L0–L2; L3 is an open market. gPTY ships a thin built-in L3 (concept routing, Inspector sessions, profile restore) but never absorbs an orchestrator’s state machine into core. Herdr-class tools are guests (they run in panes today) and substrate consumers (they drive the public API), not competitors to clone.
- Observe/display, never orchestrate. Core observes and displays agent state (badges, status); it never manages or orchestrates it (no agent runtime, no wait/block state machine). Primitives only:
paneStatus,waitForOutput, events. - Non-goals (do not re-propose without new evidence):
pane-pipeMCP tool - the concept engine already routes pane output; a generic pipe is a broad-trigger concept.layout-apply-presetworkflow automation - plugin territory (events + actions).- First-party
gpty herdr *subcommands - couples gPTY core to a foreign, versioned socket protocol with an auth model gPTY does not control (L3 creep). - Native third-party GDScript/Rust pane plugins in v1 -
PaneTypes.ALLis the layout-trust anchor; a plugin-registered registry lets profile files instantiate arbitrary code at restore time.cli_view(stdout streaming) is the v1 plugin UI; a native pane SDK needs its own trust design. ToolRunningstate via exit codes - foreground-command exits are not reliably attributable in a PTY.- Webview/WebSocket panes - a new trust boundary; not v1.
- Positioning & naming: the wedge is GUI + public-API PTY foundation + concept engine + Windows + hardening. Keep the
gptyname (2026-09 decision): rebranding is positioning, not renaming. Two-tier naming convention: the display name isgPTY(UI, docs, prose); machine-facing identifiers stay lowercase/actual -gptybinary, crates, repo, and socket paths;GPTY_*env vars;GptyTerminal;@gpty/omp-events.
Licensing
- Core is GPL-3.0-or-later (License); License exceptions holds the two section 7 additional permissions. Plugins, extensions, adapters, and pane types are not required to be GPLv3, which is why the bundled
extensions/gpty-omp-eventsships MIT andgodot/addons/gutstays MIT. Do NOT “correct” a bundled permissive license into GPLv3, and never add license headers to JSON. - Data files (profiles, workspaces, layouts, concepts, settings) carry no copyleft: user-authored ones are the user’s own, and the shipped
godot/*.jsondefaults are Apache-2.0. The engine and pane code that reads them stays GPLv3. - First-party code is GPLv3 everywhere it is declared:
license = "GPL-3.0-or-later"in everycrates/*/Cargo.toml,license=('GPL-3.0-or-later')indist/aur/PKGBUILD. Keep them in sync when adding a crate or package. - Release artifacts must carry
LICENSEandLICENSE-EXCEPTIONS.mdnext to the binaries (GPLv3 §4/§6 requires giving recipients a copy of the license)..github/workflows/release.ymlstages both copies into all three archives and publishes aSHA256SUMScovering every asset - keep both when editing that job. Every uploaded asset is also build-provenance attested (actions/attest-build-provenance, needingid-token: write+attestations: write), sogh attestation verifybinds a download to the workflow run that produced it. Supply-chain rules for the workflows: every third-party action is pinned to a full commit SHA with the version in a trailing comment, and every CI download (Godot editor, Godot export templates, Hugo) is checksum-verified against the vendor’s published sums before use - the tag is mutable, the hash is the pin.
Commits
- Format: Conventional Commits -
feat(scope):,fix(scope):,chore(scope): - Scopes:
settings,terminal,layout,workspace,sidebar,ui,gdext,core,cli,ipc,profiles,concepts,icons,ci,ai,inspector - Workflow: Use the commit skill (
skill://commit) to discover changes, group them logically, and produce correctly-formatted messages. The git hooks (pre-commit, commit-msg, pre-push) are the enforcement layer that catches bypasses. - CI gates:
pre-commitruns fast checks (fmt, workflow lint, clippy).pre-pushruns the full./scripts/ci-checksuite. Install with./scripts/install-hooksonce per clone.
Commit Discipline
- Use the commit skill (
skill://commit) as the normal workflow - it handles grouping, README freshness, and Conventional Commits. - The git hooks are the safety net: pre-commit catches fmt/lint issues, commit-msg enforces message format, pre-push runs the full CI suite.
- NEVER commit without running
./scripts/ci-checkfirst. If it fails, fix the failures before committing. cargo fmtandcargo clippyrun automatically in the pre-commit hook - but run them explicitly before staging to avoid amend churn.- If a test fails: fix the SOURCE code, not the test. Only update a test if the behavior change is intentional AND documented in the commit message body.
- Test-only commits without corresponding source changes are a red flag. If you find yourself tweaking a test “just to make it pass,” STOP - the test is catching a real issue or the test expectations are wrong. Either way, a commit must include both the source fix and the test update together.
- Push only after the full suite passes. The pre-push hook enforces this;
git push --no-verifybypasses it - use ONLY in emergencies, and expect CI to catch what you skipped. - If you must bypass: document why in the commit message body.