Linux-native multi-agent terminal with a visible Router rail, workflow feed, programmable local socket API, first-class git worktrees, and prompt-aware notifications.
ForkTTY runs coding agents in isolated workspaces, keeps task strategy, approvals, team/workflow state, and notifications visible next to the terminal, exposes a user-local Unix socket for automation, and can place long-running tasks in dedicated git worktrees without tying the UI to one agent vendor.
Website · Docs · Agent context · Download v0.2.0-alpha.18 AppImage
Status: Early alpha (v0.2.0-alpha.18). ForkTTY is Linux-only and the GTK/Ghostty runtime is now the primary implementation. The AppImage is the primary Linux download for this alpha; the Debian package remains available for Debian/Ubuntu users.
For the fastest local walkthrough, read GETTING_STARTED.md. For local quality targets, read METRICS.md. For the complete user guide, read forktty.dev/docs. For agent-oriented retrieval, start with llms.txt or the single-file llms-full.txt. This README stays focused on the project overview, install paths, quick start, and contributor commands.
- Agent-agnostic automation: the same socket API and CLI flow work for Codex, Claude Code, Pi, Antigravity CLI, OpenCode, shell scripts, and custom tools.
- Visible routing context: the GTK workbench keeps Router strategy, workflow-loop state, approvals, worker health, worker reports, notifications, and the bottom workflow feed beside the panes instead of hiding them in terminal scrollback.
- First-class worktree workflows: create, attach, remove, and merge isolated worktree workspaces through native
git2operations and optional.forktty/setup/.forktty/teardownhooks. - Native Linux terminal stack: GTK4/libadwaita shell with embedded Ghostty-backed terminals, split panes, session restore, notifications, command palette, settings, and quake mode.
- Local-first posture: no crash reporting or product event tracking, an anonymous daily usage ping that can be disabled, owner-only Unix socket permissions, bounded request/session/config files, and argv-based command execution. Optional update checks hit GitHub Releases at most once per day and can also be disabled.
The fastest paths are the prebuilt artifacts from the v0.2.0-alpha.18 release. Each release ships:
forktty-0.2.0-alpha.18-x86_64.AppImage— recommended portable Linux package.forktty-0.2.0-alpha.18-x86_64.AppImage.zsync— AppImage delta-update metadata for external AppImage managers.forktty_0.2.0.alpha.18_amd64.deb— Debian/Ubuntu package.SHA256SUMS— checksums for release artifacts.
After downloading, verify checksums:
sha256sum -c SHA256SUMSchmod +x forktty-0.2.0-alpha.18-x86_64.AppImage
./forktty-0.2.0-alpha.18-x86_64.AppImageThe AppImage always ships the vendored libghostty-vt library and prefers the
host's GUI stack: when the host provides GTK4, ForkTTY runs against the
system GTK4/libadwaita for native cursor themes, fontconfig, and portal
integration, and the bundled GTK copy is used only as a fallback on hosts
without GTK4. Set FORKTTY_APPIMAGE_GTK_RUNTIME=bundled, host, or auto
to force or debug that runtime choice. It depends on the host system for
glibc, the GSettings/GIO data tree, Wayland/X11 session services, fontconfig,
the
OpenGL/Vulkan/Mesa driver stack, and desktop notification services.
It is the primary downloadable artifact for alpha releases and works on
most modern distros that ship a recent glibc, but it should
still be tested on the target distro/desktop environment before being
relied on.
ForkTTY checks GitHub Releases for updates at most once per day by default.
AppImage installs can update in place after confirmation: ForkTTY downloads
the new AppImage and SHA256SUMS, verifies SHA256, then atomically replaces
the current file. Non-AppImage installs open the release page instead. AppImage
managers such as Gear Lever continue to work when they launch ForkTTY from a
stable writable AppImage path; if ForkTTY sees an extracted, read-only, or
otherwise unsafe path, it falls back to the release page and leaves the manager
in control.
ForkTTY defaults to the OpenGL GTK renderer (GSK_RENDERER=ngl). If the
AppImage launches but the GTK interface renders incorrectly, override it with a
different renderer from a terminal:
GSK_RENDERER=gl ./forktty-0.2.0-alpha.18-x86_64.AppImageUse GSK_RENDERER=cairo only as a last resort: the software renderer is slower
and retains far more memory under heavy terminal redraws.
The .deb targets Debian 13/Trixie or newer and Ubuntu 24.04 LTS or newer.
Debian 12/Bookworm is below the package baseline because it does not provide
libadwaita 1.4+.
sudo apt install ./forktty_0.2.0.alpha.18_amd64.deb
# or, if apt cannot read the file path directly:
sudo dpkg -i forktty_0.2.0.alpha.18_amd64.deb
sudo apt -f installThe package installs the forktty binary, the desktop entry, and the
icon. Removing it (sudo apt remove forktty) cleans up
/usr/bin/forktty and the desktop integration.
Requirements:
- Linux
- Rust 1.96+
- GTK4 and libadwaita development files
git, Zig, and the full Ghostty source submodule for the vendored Ghostty terminal libraries- No system Ghostty package is required; source and packaged builds use the pinned vendored Ghostty libraries
Developer repository checks also expect the full Ghostty source submodule:
git submodule update --init vendor/ghosttyDebian / Ubuntu:
sudo apt install build-essential libssl-dev libgtk-4-dev libadwaita-1-dev git zig desktop-file-utilsFedora:
sudo dnf install gcc gcc-c++ openssl-devel gtk4-devel libadwaita-devel git zig desktop-file-utilsArch / CachyOS:
sudo pacman -S base-devel openssl gtk4 libadwaita git zig desktop-file-utilsSource builds require libadwaita 1.4+, matching Debian 13/Trixie, Ubuntu 24.04 LTS, and newer distro packages. Release AppImages bundle GTK4, libadwaita, and gtk4-layer-shell so terminal panes do not depend on those host packages.
Clone and run:
git clone https://github.com/Lucenx9/forktty.git
cd forktty
git submodule update --init vendor/ghostty
scripts/ghostty-gtk-lib-probe.sh --ensure --print-path
cargo run -p forktty-ui-gtkPackaged builds are GTK/Ghostty-only. The experimental WebKitGTK browser pane
remains in source behind the opt-in browser feature; it is not shipped in
the AppImage or .deb for this alpha.
For the explicit terminal-only build used by release artifacts:
cargo run -p forktty-ui-gtk --no-default-features --features gtk-ghosttyFor the source-only browser experiment, install WebKitGTK 6 development files and opt in:
cargo run -p forktty-ui-gtk --no-default-features --features browserBuild the Debian package locally:
bash scripts/build-deb.sh
sudo dpkg -i target/packaging/deb/forktty_*.debscripts/build-deb.sh and scripts/build-appimage.sh call
scripts/ghostty-gtk-lib-probe.sh --ensure --print-path before packaging and
fail if ghostty-gtk-embed.so cannot be built, located, or verified. The
verified library is installed into usr/lib beside the forktty binary so
installed terminal panes can load it through the binary RUNPATH without
FORKTTY_GHOSTTY_GTK_LIB.
Debian packages also install ForkTTY copyright/license information and
third-party notices under /usr/share/doc/forktty/.
Build the AppImage locally (requires appimagetool on
PATH, or APPIMAGETOOL=/path/to/appimagetool):
bash scripts/build-appimage.sh
./target/packaging/appimage/forktty-*-x86_64.AppImageSet APPIMAGE_UPDATE_INFO=1 when building release-style AppImages with
embedded update metadata; this requires zsyncmake on PATH and emits a
matching .zsync file.
The AppImage includes the same ForkTTY copyright/license information and
third-party notices under usr/share/doc/forktty/ inside the AppDir/AppImage.
After install, confirm the runtime looks healthy:
forktty --version
forktty doctorforktty doctor is a local-only inspector. It reports the resolved
config, session, socket, hook config paths, and known recovery behaviors,
and exits 0 on a clean environment or 2 with explicit warnings. Use
forktty --json doctor when you also need the socket doctor report with
environment, executable, hook config, MCP config, and agent skill paths.
ForkTTY opens the current directory as the main workspace. Use the
command palette for most navigation and pane actions:
Ctrl+Shift+P: command paletteCtrl+Shift+N: new workspaceCtrl+Shift+O: open workspaceCtrl+Shift+H: split pane rightCtrl+Shift+E: split pane downCtrl+Shift+T: new tab in the focused paneCtrl+Shift+W: close paneCtrl++/Ctrl+=,Ctrl+-,Ctrl+0: zoom terminal panesCtrl+BorF9: toggle workspace sidebar- Agents: titlebar button or command palette
Ctrl+Shift+M: notificationsCtrl+?(F1also works): keyboard shortcutsCtrl+,: settingsF10: main menu when focus is outside terminal content; terminal panes keep plainF10for TUI apps
The same forktty binary speaks the local socket API. Diagnostic
commands work even when the GTK app is not running:
forktty --help
forktty --version
forktty doctor # local diagnostics, no socket required
forktty worktree-doctor --cwd "$PWD" --json # read-only worktree diagnostics, no socket required
forktty ping # check the running daemonWorkspace, surface, worktree, notification, and metadata commands talk to the live socket:
forktty list
forktty focus "Workspace 2"
forktty ssh [email protected]
forktty surfaces --workspace-name main
forktty agents --workspace-name main
forktty agent-health --workspace-name main
forktty resume-agent --surface-id <surface-id>
forktty agent-reclaim-plan --workspace-name main --min-idle-ms 600000
forktty read-screen --surface-id <surface-id>
forktty capture-tail --surface-id <surface-id> --lines 80
forktty tree --workspace-name main
forktty top --workspace-name main
forktty split-surface --axis vertical
forktty new-tab
forktty send-text "cargo test\n"
forktty worktree-status
forktty notify --title "Input needed" --kind prompt "Blocked on test fixture"
forktty set-status --key agent:codex --label Codex --value Running --color blue
forktty set-progress --key build --label Build --value 42 --total 100
forktty statusline
forktty status explain --tail-lines 20
forktty status watch --count 3 --interval-ms 2000
forktty identify --json
forktty wait agent-status --status needs_input --timeout-ms 30000
forktty context-snapshot --workspace-name main --tail-lines 0 --json
forktty task-plan "fix this bug and verify it" --cwd "$PWD" --json
forktty task-apply --run-id router-run-1 --plan-json '<plan-json>' --request-approval "fix this bug and verify it"
forktty feed respond '<approval-id-from-request>' --decision approve
forktty task-apply --run-id router-run-1 --plan-json '<plan-json>' --approval-id '<approval-id-from-request>' "fix this bug and verify it"
forktty task-apply --run-id router-run-1 --plan-json '<plan-json>' --approved start_run "fix this bug and verify it"
forktty task-apply --run-id router-run-1 --plan-json '<team-plan-json>' --cwd /path/to/repo --approved start_run,launch_parallel_workers --submit "review this implementation"
forktty cleanup orchestration --dry-run
forktty cleanup orchestration --apply
forktty workflow-loop-set loop-runtime --stage verify --iteration 2 --max-iterations 4
forktty workflow-loop-iteration-start loop-runtime --stage implement
forktty workflow-loop-iteration-done loop-runtime passed --summary "all checks passed"
forktty team ask review-team claude-review --agent claude --task-id review-head --prompt "Review HEAD read-only" --submit
forktty team review review-team claude-review --agent claude --task-id review-head --commit HEAD --submit
forktty team watch review-team --stale-after-ms 120000 --limit 10
forktty team finish review-team --dry-run
forktty team finish review-team --close-workers
forktty examples
forktty completions bash
forktty log --level warn "Waiting for reviewer input"
forktty notifications
forktty capabilities
forktty eventsThe socket method stability map lives in docs/socket-api.md. In short, discovery/context/surface reads and basic workspace control are stable-for-alpha; router/team/workflow orchestration remains public alpha while the model hardens.
ForkTTY agents and scripts can ask the local task router for a strategy before
choosing manual modes. forktty task-plan "fix this bug and verify it" --json
and the MCP task_strategy_plan tool return whether the task should stay solo,
use a workflow loop, add a reviewer, create a team, or isolate work in a
worktree. The planner is read-only, returns the selected router profile
(balanced, fast, conservative, parallel, or review_heavy) plus ranked
candidate strategy scores with factor breakdowns, scores each ready harness
assignment by role with factor breakdowns, uses configured team provider order
as the assignment tie-break, respects each harness's declared parallel session
capacity before selecting multi-role parallel plans, infers dirty git state
from the selected surface/workspace cwd when repo_dirty/--repo-dirty is
omitted, or from an explicit absolute --cwd / MCP cwd inside a Git
repository already represented by an open ForkTTY workspace, surface, or
effective project cwd. It also infers likely user-visible edit
intent and clear router profiles from mutating task_kind hints and
high-confidence goal wording when omitted,
infers advisory last-known-good strategy/harness evidence from completed
task-strategy workflows when available, and keeps reviewer strategies honest by
including a reviewer assignment. Pass
--task-kind <kind> or MCP/socket task_kind when an agent has already
normalized clear user intent (for example from a non-English request) into
stable categories such as bugfix / focused_bugfix, feature /
feature_implementation, review / review_only, or research /
parallel_research; ForkTTY still scores strategy, harnesses, layers, and
approvals from that structured hint instead of keyword guessing across
languages. Pass
--profile <profile> or MCP/socket router_profile only when you want to bias
the automatic scorer explicitly. Scripts and MCP agents with real runtime
evidence can pass --last-known-good-json/last_known_good to override or
enrich the inferred advisory stickiness for a previously successful strategy or
harness, and per-harness
--harness-signals-json/harness_signals:
cooldown is a soft assignment penalty, while locked_out excludes that
harness for the current task/mode. Last-known-good only adds a small
explainable score factor; readiness, cooldown, lockout, task fit, and approvals
still win. After review, forktty task-apply --run-id <id> --plan-json '<plan-json>' --cwd <repo> --approved start_run "<goal>" and the
MCP task_strategy_apply tool can stage the returned plan as visible
workflow/team/task/message state. When a client wants ForkTTY to ask first,
--request-approval/request_approval: true records a pending Feed approval and
returns status: "blocked" without mutating workflow/team state; after
forktty feed respond <approval-id> --decision approve, retry apply with
--approval-id <approval-id>/approval_id. The returned approval id is bound
to that run id, goal, plan, target scope, and submit mode; request a new
approval when any of those change. An approval id requested for a superset of
the currently missing approvals can satisfy the remaining approvals when the
caller also supplies explicit attestations for part of the same request.
Approval decisions are accepted only while the Feed approval is still pending;
dismissed, stale, approved, or denied entries cannot be reused as fresh
authorization. If a caller instead retries
with an equivalent explicit --approved / approved attestation, ForkTTY
dismisses the now-superseded pending Feed approval so it no longer raises a
pending_approval risk flag. Apply recomputes dirty-repo edit isolation from
the selected surface/workspace plus any explicit --cwd / cwd, normalized
plan task_class, mutating strategy shape, and high-confidence goal wording,
then recomputes required approvals from the requested operation and effective
plan shape, so dirty editing tasks cannot drop create_worktree and
multi-worker submit cannot drop launch_parallel_workers from the JSON to
bypass review.
--approved/approved is a programmatic caller attestation; use
--request-approval and retry with --approval-id when a
Feed-backed human approval is required. Pass --cwd/cwd when the repository
target differs from the selected ForkTTY pane and is already represented by an
open ForkTTY workspace, surface, or effective project cwd; submit-mode workers
launch at the canonical form of that path, and role prompts include that cwd
without creating a workspace or worktree.
Primary review goals such as "Review ..." remain read-only review work, so a
dirty repository does not force worktree isolation unless the goal is actually
editing/user-visible work.
Pass --submit/submit: true for a supported
team plan:
ForkTTY launches visible worker panes, assigns tasks, queues deterministic role
prompts, and dispatches them through the team mailbox with provider-aware Enter
handling. If the plan has layers.worktree: true, pass
--worktree-name <name>/worktree_name for an already-open ForkTTY worktree
workspace; apply will reject missing or unopened worktrees before mutating
state, and worker task text names both that worktree and its effective cwd.
Submit retries reuse a live deterministic worker only when its harness,
role, task, worktree, launch cwd, effective target cwd, and status still match
the current assignment;
otherwise apply returns conflict before dispatching a prompt to the wrong
pane.
Worktree creation, push, merge, destructive commands, and out-of-scope edits
require later explicit approval.
forktty cleanup orchestration and MCP orchestration_cleanup are
dry-run-first maintenance entry points for stale team/workflow records left
after interrupted agent runs. They only mark records whose worker surfaces no
longer exist, supersede their pending prompts, and report live or unrecorded
worker surfaces for manual review; --apply/apply=true is required before
they write.
forktty team ask and forktty team review compose the existing team socket
methods for common coordination flows: create/update the team, create the task,
launch a fresh worker surface, assign the task after launch, queue the prompt,
and dispatch it. Submit mode uses provider-aware terminal input: providers that
accept it reliably receive text plus carriage-return Enter in one write, while
Codex, Claude, and Pi receive the text, a short settle, and a separate Enter so their
TUIs start the staged prompt reliably. Freshly launched provider TUI workers
also get a brief initial prompt settle before the first dispatch. Human CLI
output names the worker, selected provider when known, task, target surface,
and whether the prompt was dispatched or submitted so a leader can tell which
visible pane to monitor without opening JSON.
Re-run them to launch a new worker;
use team-message-send plus team-message-dispatch for follow-up prompts to
an existing worker. Context snapshots include compact team_summaries rows so
leaders can scan worker/task/message counts without first calling
team.summary for each team; full team records with mailbox message bodies are
included only when context.snapshot receives include_team_details: true.
Superseded task-strategy prompts remain in full team history but are excluded
from normal inbox and pending-message counts.
team_summaries rows include consistency_warnings when a team marked done
still has active workers, open tasks, or pending messages, or when an active
team has no open work left. Snapshots raise team_consistency_warning in
risk_flags for the same condition. forktty team finish / team.finish
performs the finalization preflight in one step: --dry-run shows the planned
actions, blockers, and cleanup errors without mutation; non-dry-run
finalization blocks on open tasks, pending messages, or live-looking workers
unless --force is supplied, closes only current-runtime launch-owned
disposable worker panes with --close-workers, normalizes missing worker
surfaces as closed, and marks the team done only after all requested worker
surface closes succeed. If a later worker close fails, any already-closed
runtime surfaces are restored and the team store remains unchanged. Closing a
launch-owned worker pane keeps the normal surface.close replacement behavior,
including replacement terminals for root panes in inactive workspaces; if that
replacement terminal cannot be spawned, ForkTTY returns an error before closing
the worker surface.
Context snapshots also include compact workflow_summaries rows. Full workflow
records with durable memory, plan steps, and evidence are included only when
context.snapshot receives include_workflow_details: true. Workflow summary
rows include surface_present, stale_binding, and consistency_warnings,
with workflow_consistency_warning in risk_flags when a running workflow has
a completed plan, an active workflow is bound to a missing surface, or a
terminal workflow still has open steps. Snapshot feed rows are compact by default: approvals and
notifications remain, while status and progress trace rows are available with
include_feed_trace: true.
For closed loops, workflow.loop.set / workflow_loop_set /
forktty workflow-loop-set records bounded loop metadata on an existing
workflow: recipe, stage, iteration budget, stop reason, and compact gate
counts. task.strategy.apply bootstraps loop-enabled plans with pending
verifier-contract gates, and the CLI wrappers workflow-loop-gate,
workflow-loop-step-done, workflow-loop-iteration-start,
workflow-loop-iteration-done, and workflow-loop-publish update that state
without requiring hand-written JSON. It is deliberately state-only: ForkTTY does
not run a hidden scheduler, execute commands, push, merge, or approve actions
from loop state. Snapshots include loop_summaries by default and raise loop
risk flags for failed gates, blocked or human-needed stages, exhausted budgets,
stale workflow surface bindings, loop adoption gaps, and successful loop stop
reasons that lack workflow evidence. Loop summaries omit full workflow goals and
gate notes; detailed workflow memory remains opt-in, and moving to a new
iteration clears prior gate results and stop reason unless replacements are
supplied in the same request.
Workspace, surface, agent, and agent-health rows expose
effective_project_cwd, preferring the tracked agent resume_cwd over the
workspace directory when they differ; the GTK sidebar uses the same effective
path for a focused tracked agent so a workspace launched from ~ can still
display the project directory where the agent is actually working. That value is
diagnostic context: worktree authorization and team worker launch placement use
visible workspace roots, explicit launch/apply cwd, and the selected
surface's recorded terminal cwd rather than hook-reported resume_cwd
metadata.
team.worker.health includes
surface_present, surface_runtime_present, surface_ready, and a derived
final_state such as shutdown_requested, closed, starting,
surface_missing, or stale so cleanup decisions do not require interpreting
raw worker fields; cleanup treats a worker surface as live only when it still
exists in the workspace model and the terminal backend still reports that
surface, while surface_ready remains a separate startup/readiness signal.
team.worker.shutdown uses the same provider-aware submit behavior by default;
its close_surface
option, exposed by CLI forktty team-worker-shutdown --close, immediately
closes only surfaces that this ForkTTY runtime created through
team.worker.launch, not manually attached user panes or stale persisted launch
records after restart. If the surface cannot be closed, the worker store state
is left unchanged instead of being marked shutdown-requested.
For smaller control loops, system.identify/forktty identify returns the
canonical target workspace/surface, caller id validation, current agent binding,
and effective_project_cwd. ForkTTY pane environment ids are treated as caller
context, so stale caller surface ids fall back to the active workspace focus
instead of failing the read. forktty wait agent-status polls the persisted
agent lifecycle with bounded timeout/interval values through short
context.snapshot reads and no terminal text reads.
The titlebar Agent HUD shows persisted agent sessions across workspaces, highlights
sessions that need input, and can focus or resume a tracked agent. The GTK HUD
and sidebar collapse raw lifecycle values into scan-friendly labels such as
Working, Needs input, Done, and Idle; the socket/CLI surfaces keep the raw
lifecycle values (running, idle, needs_input, ended, or unknown) when
a provider session id has been persisted. Hook events drive the live state, and
terminal child exit marks attached sessions ended. forktty agents and
forktty agent-health also expose persisted resume_cwd, last_activity_ms,
source, response observed_at_ms, and nullable age_ms so delayed
agent state can be compared with terminal evidence. Agent rows also include
lifecycle_evidence, a diagnostic block that correlates the persisted
lifecycle/freshness data with the current workspace/provider status row,
permission mode, and resume-readiness reason when available. For Antigravity CLI,
resume_cwd comes from the hook payload's workspacePaths, because agy
executes the generated wrapper scripts from ~/.gemini/config rather than from
the project directory.
After a ForkTTY restart, restored terminal panes with a supported persisted
agent session respawn through the provider's argv-only resume command, such as
codex resume -C <resume_cwd> <session-id> when Codex hook metadata recorded
the session working directory. Providers without a cwd flag, such as Claude
Code, are spawned with resume_cwd as the child process directory. If a legacy
ForkTTY session has no persisted resume_cwd yet, Codex resumes can still infer
it from Codex's local session_meta JSONL when that project directory still
exists.
When provider hooks record the exact bypassPermissions mode, ForkTTY preserves
that elevated mode on agent-health, explicit resume, and restore-time
auto-resume by emitting the documented Codex and Claude Code bypass flags; other
permission mode strings remain metadata and never become argv.
forktty agent-reclaim-plan is read-only: it identifies old idle sessions that
are locally resumable and explains why other sessions are protected, but it does
not suspend or close panes.
Source-only builds with the browser feature expose experimental browser-pane
automation over the same socket. This is intentionally not included in the
prebuilt AppImage or .deb for the current alpha:
forktty browser open --profile Default https://example.com
forktty browser navigate <surface-id> https://www.rust-lang.org
forktty browser snapshot <surface-id>
forktty browser click <surface-id> <ref>
forktty browser fill <surface-id> <ref> "value"
forktty browser eval <surface-id> "document.title"
forktty browser profile list
forktty browser profile create Work
forktty browser history list
forktty browser bookmark add https://www.rust-lang.org
forktty browser import discoverThe socket CLI and agent hook bridge are native Rust code in the
forktty binary, so source checkouts and packaged builds do not
require Node.js.
forktty capabilities includes the socket method list plus a
provider_capabilities matrix for the supported providers (codex, claude,
pi, opencode, antigravity, and grok), including their launch program, whether
ForkTTY has safe resume support, and whether the provider exposes a dedicated
cwd resume flag, plan-mode reviewer support, and parallel session capacity. It
also reports available_on_path, the resolved executable path when present,
configured command overrides for harnesses installed outside the ForkTTY process
PATH, disabled/missing reasons, and the active
team_provider_policy used when team launches omit --agent. It also includes
pty_persistence, a read-only diagnostic block showing whether
general.persist_terminal_processes is configured and whether a supported
broker such as dtach is currently available. Providers
without a cwd flag, such as Claude Code, can still resume in ForkTTY's recorded
process cwd when resume_cwd is available. Pi resume uses the documented
pi --session <id> flow. forktty identify is the smallest CLI read for
"where am I in ForkTTY?", while
forktty wait agent-status gives scripts and agents a bounded lifecycle wait.
ForkTTY can also expose the same local automation surface as MCP tools for agents that support stdio MCP servers:
forktty mcp # run the MCP server on stdio
forktty mcp setup # register Codex, Claude, Antigravity
forktty mcp setup codex claude --dry-runThe MCP server is local-only: it opens no network listener and bridges validated
tool calls to the owner-only ForkTTY Unix socket. It exposes identify,
workspace/surface inspection, topology tree, terminal read/capture, persisted
agent-session inspection and explicit resume into a new tab, compact status
summaries, pane split/focus/send-text, worktree create/attach/remove/merge,
task strategy planning/apply, notifications, and status_set.
Codex MCP setup preserves hand-edited TOML comments/formatting and uses the
larger MCP config size budget for $CODEX_HOME/config.toml or
~/.codex/config.toml. If setup registers an AppImage launcher, the managed
MCP server env includes APPIMAGE_EXTRACT_AND_RUN=1 so persistent MCP clients
do not keep a FUSE AppImage mount alive.
XDG_RUNTIME_DIR, FORKTTY_SOCKET_PATH,
FORKTTY_WORKSPACE_ID, and FORKTTY_SURFACE_ID are honored as defaults when
the MCP host launches from a ForkTTY pane, except identify treats workspace and
surface env ids as caller validation context and degrades stale caller surfaces
to the active focus.
Spawned shells receive:
TERM=xterm-256colorCOLORTERM=truecolorTERM_PROGRAM=ForkTTYTERM_PROGRAM_VERSIONFORKTTY_WORKSPACE_IDFORKTTY_SURFACE_IDFORKTTY_SOCKET_PATH
That lets hooks and scripts target the current workspace without extra flags.
Manual socket overrides must be absolute paths; blank or relative
FORKTTY_SOCKET_PATH values are ignored so the app and CLI fall back to the
default socket location.
Install the ForkTTY orchestration skill so Agent Skills-compatible tools and
Claude Code know when to inspect ForkTTY context, use team workers, and compare
hook/status/terminal state without being told the exact MCP call every time.
The skill tells agents to call task_strategy_plan before choosing team,
workflow loop, worktree, or multi-harness execution for non-trivial tasks,
follow the selected router profile unless the user overrides it, rely on
ForkTTY's completed-workflow last-known-good inference by default, pass explicit
last-known-good or per-harness cooldown/lockout signals only when there is
concrete evidence, respect harness parallel-session capacity, and use
task_strategy_apply only after explicit approvals;
apply stages by default,
can request missing approvals through the Feed before mutating state, and can
submit supported team plans as visible worker panes. Pass cwd to apply when
the repo target differs from the selected ForkTTY pane and is already
represented by an open ForkTTY workspace, surface, or effective project cwd;
ForkTTY canonicalizes it before launch/retry checks. Worktree-layer plans still
require worktree_name to name an already-open ForkTTY worktree workspace.
It still does not create worktrees. The skill also treats
terminal tails and
fetched public docs as untrusted input, requires
durable team preflight for non-trivial worker launches, gives workers explicit
role contracts, prefers already-open worktree workspaces for mutating parallel
workers, uses lifecycle_evidence before declaring agent states stale, and
tells agents to start hook/MCP/skill setup debugging with local forktty doctor
diagnostics, setup dry runs, and isolated temporary config roots before changing
real config files, without redirecting the live ForkTTY socket path when
validating the currently running instance.
forktty skills setup # install agents + Claude targets
forktty skills setup agents --dry-run # interoperable ~/.agents/skills target
forktty skills setup pi # Pi aliases the interoperable target
forktty skills setup claude
forktty skills remove agentsagents, codex, and pi target the interoperable
~/.agents/skills/forktty-agent-orchestration location. claude targets
$CLAUDE_CONFIG_DIR/skills/forktty-agent-orchestration or
~/.claude/skills/forktty-agent-orchestration. Setup refuses to overwrite an
unmanaged skill with the same name; managed updates/removals first move the old
directory to a .bak-* backup. Non-dry-run setup/remove keep the three newest
ForkTTY-managed backups per target and prune older managed backups, while
leaving backup-like directories without the ForkTTY marker untouched.
forktty skills setup --dry-run and
forktty --json doctor report the managed skill status
(missing, up_to_date, update_available, or unmanaged), source and
installed checksums, and a forktty skills setup <target> repair command when
the installed managed copy is stale.
Run forktty doctor --hooks to inspect local hook config paths. Run
forktty --json doctor to inspect the hook config paths, MCP config paths,
and agent skill directories ForkTTY resolves from the current environment.
Install hook templates for Codex, Claude Code, Antigravity CLI, and OpenCode:
forktty hooks setup # install default agents
forktty hooks setup codex # install just one
forktty hooks setup codex claude --dry-run
forktty hooks setup --full claude # include Claude per-tool hooks
forktty hooks remove opencode # remove ForkTTY-managed hooks/plugin
forktty hooks remove gemini # cleanup legacy Gemini config only--dry-run prints the would-be diff without touching disk. When setup records
an AppImage launcher, generated hook commands set APPIMAGE_EXTRACT_AND_RUN=1
for the ForkTTY CLI child so short hooks do not keep a FUSE AppImage mount
alive. Claude Code setup uses the lifecycle profile by default, avoiding
blocking per-tool hooks on every tool call; pass --full to include
PreToolUse, PostToolUse, PostToolUseFailure, and PostToolBatch.
Re-running setup migrates Claude to
the lifecycle profile unless --full is passed. hooks remove removes only
ForkTTY-managed entries/plugins and leaves unrelated agent hooks in place.
hooks remove gemini is kept only to clean legacy ForkTTY-managed
~/.gemini/settings.json entries from older releases; Gemini setup remains
unsupported.
The installer merges commands into the agent's own config file:
| Agent | Destination |
|---|---|
| Codex | $CODEX_HOME/hooks.json or ~/.codex/hooks.json |
| Claude Code | $CLAUDE_CONFIG_DIR/settings.json or ~/.claude/settings.json |
| Antigravity CLI | ~/.gemini/config/hooks.json plus wrapper scripts in ~/.gemini/config/forktty-hooks.generated/ |
| OpenCode | $OPENCODE_CONFIG_DIR/plugins/forktty.generated.js or ~/.config/opencode/plugins/forktty.generated.js |
When HOME is overridden, the ~ defaults are resolved under that home
directory. Existing configs are written atomically (tmp + rename) and a
timestamped .bak-* backup is created when content changes.
On app startup, ForkTTY shows a reminder notification when no ForkTTY-managed agent hooks are installed, or when installed hooks point at a stale launcher.
Diagnose and exercise installed hooks:
forktty hooks doctor codex # inspect socket, launcher, env, hook config
forktty hooks test codex # round-trip a status update through the socketEach agent's hook commands honor a per-agent disable variable:
FORKTTY_CODEX_HOOKS_DISABLED=1FORKTTY_CLAUDE_HOOKS_DISABLED=1FORKTTY_ANTIGRAVITY_HOOKS_DISABLED=1FORKTTY_OPENCODE_HOOKS_DISABLED=1
Hooks report status, progress, logs, and prompt notifications through
the same local socket pipeline. Manual hook-event commands can pass
--socket <path> when they run outside a ForkTTY-spawned shell.
- Native GTK4/libadwaita desktop shell with embedded Ghostty-backed terminals.
- Recursive split panes, pane focus/close, command palette, settings dialog, notification panel, workspace sidebar, Router rail, and workflow feed.
- Quake/dropdown mode through config and F12 where global shortcuts are supported.
- Direct Unix socket JSON-RPC server for workspace (including SSH remote workspaces), surface, terminal read/capture, topology tree/top health inspection, pane-tab, notification, worktree, metadata, persisted agent-session inventory/resume, compact status summaries, context identify, read-only task strategy planning, event-stream, and capabilities; CLI wrappers add bounded lifecycle waits over read-only socket calls.
- Agent HUD in the GTK titlebar for lifecycle, last activity, attention, focus, and resume across workspaces.
- Git worktree create/attach/remove/merge/status with dirty-state protection and hook execution inside verified worktrees. Setup hooks are advisory; teardown hook failures or teardown-created dirty state block removal.
- Session restore for workspace order, active workspace, pane tree, focused surface, cwd, branch, and worktree metadata.
- Prompt-aware notifications from ForkTTY hooks and terminal events, bounded visible prompt fallback, Ghostty bell, and hook/socket events. The notification panel groups prompts/current-workspace/history and its latest-target action prioritizes unread prompts before lower-urgency history.
- Source-only experimental WebKitGTK6 browser panes (behind the
browserfeature) with scriptable snapshot/click/fill/eval verbs, per-profile persistent WebKit sessions, profile CRUD, history/bookmark socket plus CLI access, and history/bookmark import from local Firefox/Chromium-family profiles. - Bounded config/session/socket handling and local-only privacy defaults.
Config file: ~/.config/forktty/config.toml. All fields are optional. The
Settings dialog intentionally does not edit general.shell; change the shell by
editing this file or your login shell environment.
[general]
shell = "/bin/bash"
worktree_layout = "nested" # "nested", "sibling", or "outer-nested"
enable_pr_lookup = false
notification_command = ""
persist_terminal_processes = false
[appearance]
persistent_scrollback_lines = 0
sidebar_position = "left" # "left" or "right"
sidebar_visible = true
show_orchestration_rail = true
show_workflow_feed = true
window_mode = "normal" # "normal" or "quake"
[notifications]
desktop = true
sound = true
blocked_terminal_apps = []
blocked_terminal_types = []
[team]
default_agent = "auto" # "auto", "codex", "claude", "pi", "opencode", "antigravity", or "grok"
provider_order = ["codex", "claude", "pi", "opencode", "antigravity", "grok"]
auto_fallback = true
disabled_agents = []
[updates]
auto_check = true
[telemetry]
anonymous_ping = truenotification_command is split with shell_words; ForkTTY does not use sh -c. The first token must be an absolute executable path. Notification title/body are passed through FORKTTY_NOTIFICATION_TITLE and FORKTTY_NOTIFICATION_BODY; OSC 99 f/t metadata is exposed as FORKTTY_NOTIFICATION_TERMINAL_APP and FORKTTY_NOTIFICATION_TERMINAL_TYPES_JSON. blocked_terminal_apps and blocked_terminal_types suppress terminal-originated OSC 99 notifications whose exact f application or t type matches one of the listed strings.
The [team] section controls visible team worker launch defaults. Omit
--agent or pass --agent auto to let ForkTTY choose the first configured
provider whose harness is resolvable from the ForkTTY process PATH or a
configured provider_commands override; use an explicit provider to override
the policy. Settings > Agents shows the same default, fallback, provider order,
disabled providers, direct command overrides, and harness detection.
persistent_scrollback_lines is off by default; when set above 0, ForkTTY stores a bounded plain-text tail per surface in session-v2.json and restores it before the fresh shell starts. For embedded Ghostty panes, the current GTK text ABI exposes this polling tail through Ghostty's visible-text path, so it preserves recent visible output rather than an off-screen scrollback slice; use read-screen/surface_read_text with scope=all when a bounded full text read is needed. persist_terminal_processes is also off by default and can be toggled from Settings > Worktrees; when set to true and dtach is available on an absolute PATH entry, plain terminal panes run under a detach/reattach broker so generic shells, dev servers, REPLs, editors, and long-running commands can survive a GTK UI restart and re-attach on relaunch. AppImage-launched brokers close inherited AppImage runtime file descriptors before dtach starts, so surviving brokers do not keep the FUSE AppImage mount alive after the GTK window closes. Explicit pane close/restart terminates the matching ForkTTY-managed broker process tree and removes the per-surface broker socket, so a later reused surface id starts fresh instead of attaching to stale detached state. Disabling the setting cleans stale detached sessions while preserving currently visible panes until they close; closing the GTK window with the setting disabled cleans visible managed brokers too. Starting with the setting disabled cleans old managed sessions before restore. Agent panes continue to use provider resume, and SSH, browser, project-action, and team-worker panes are not wrapped. If dtach is missing, ForkTTY falls back to normal ephemeral terminal spawning. Live embedded panes follow Ghostty's scrollback-limit budget (10 MB by default) and scrollbar = system|never preference, so mouse-wheel scrollback and the vertical scrollbar come from Ghostty config while legacy ForkTTY scrollback_lines is treated as a compatibility key and omitted from new saves. Terminal font, colors, cursor/faint opacity, bell behavior, mouse scroll multiplier, cell size adjustments, and inactive split dimming come from Ghostty's config (~/.config/ghostty/config.ghostty or the legacy ~/.config/ghostty/config) when present, including config-file, theme, named colors, 16-color palette entries, cursor-opacity, faint-opacity, bell-features, mouse-scroll-multiplier, adjust-cell-width, adjust-cell-height, unfocused-split-opacity, and unfocused-split-fill; no system Ghostty install is required. Legacy ForkTTY font/theme/scrollback/bell/renderer keys are kept only for config compatibility and omitted from new saves. Terminal panes require the embedded Ghostty GTK widget; if ghostty-gtk-embed.so is missing or fails to load, panes report a spawn failure rather than opening with the old renderer.
updates.auto_check = true checks GitHub Releases no more than once every 24 hours. The stamp is written on both success and failure so offline machines are not probed on every launch.
telemetry.anonymous_ping = true sends at most one GTK-startup ping per UTC day to https://forktty.dev/api/telemetry/ping. The JSON body is limited to schema, kind, app, version, and date; it contains no install id, username, hostname, cwd, repository path, branch, shell, agent metadata, terminal buffer, socket payload, or crash data. Set it to false to disable the ping.
See SPEC.md for the full list of validated fields and their bounds.
GTK/Ghostty sessions are stored as:
~/.local/state/forktty/session-v2.json
ForkTTY imports legacy session.json when present, but saves the native runtime as v2. By default restore re-spawns fresh Ghostty-backed terminals; scrollback restore is limited to the opt-in plain-text tail controlled by persistent_scrollback_lines, and embedded Ghostty panes currently source that tail from visible text rather than off-screen scrollback. With general.persist_terminal_processes = true and dtach available, plain terminal process trees survive through the broker and restored panes re-attach by persisted surface id. If the setting is false on startup, old ForkTTY-managed broker sessions are cleaned before restore. Corrupt or structurally invalid session files are quarantined.
- Local Linux desktop threat model; same-user processes remain part of the local trust boundary.
- Unix socket defaults to
$XDG_RUNTIME_DIR/forktty.sockwith/tmp/forktty-<uid>/forktty.sockfallback and owner-only permissions. - Socket request lines, config files, and session files are size bounded.
- Shell paths, hooks, and custom notification commands use validated argv execution, not shell pipelines.
- Worktree names, socket-provided repo paths, and hook locations are validated before mutation or execution.
- ForkTTY makes no crash-reporting or product event-tracking network calls. With
telemetry.anonymous_ping = true, the GTK app sends one anonymous daily usage ping; set it tofalseto disable it. Withupdates.auto_check = true, the GTK app checks GitHub Releases at most once per day; browser panes and PR lookup remain optional/user-directed network paths. The shipped AppImage and.debdo not embed a browser runtime.
See SECURITY.md and PRIVACY.md.
- Linux only. There are no supported macOS or Windows builds.
- libadwaita 1.4+ is required by the native terminal integration.
- The AppImage ships a bundled GTK4/libadwaita fallback plus Ghostty and gtk4-layer-shell, but prefers a host GTK stack when available and still relies on the host's glibc, fontconfig, OpenGL/Vulkan/Mesa driver stack, display-server libraries, and desktop session services. Test it on the target distro/desktop environment; prefer the
.debon Debian/Ubuntu when package-manager integration matters. - PTY/process persistence is opt-in for plain terminal panes through
general.persist_terminal_processesand requiresdtach; by default restored sessions spawn fresh shells. Scrollback persistence is opt-in, plain-text only, and bounded. - OSC 9 and basic OSC 99 terminal notifications are parsed from the Ghostty-owned PTY stream and rate-limited per surface; OSC 99 title/body base64 payloads and same-id title/body chunks are decoded with multipart title/body kept separate, same-id update/close controls affect ForkTTY's notification model, and in-app Open/Dismiss/Clear All plus basic same-id buttons can send OSC 99 reports. Targeted desktop notifications expose a best-effort Open action, notification dismiss/clear closes matching desktop and OSC 99 tracked notifications, prompt feed approvals distinguish pending/approved/denied/dismissed/stale, icon names, application-name icon fallback, application/type filtering metadata, occasion filtering, urgency, expiry, and sound metadata feed notification handling, positive
wexpiry values dismiss in-app notifications, and boundedp=icondata can be cached byg; broader chunk lifecycle behavior remains partial. - Quake global shortcuts and layer-shell placement depend on desktop/compositor support.
- Agent hibernation/suspend UI, provider-side session existence checks, full theme customization, multi-window, and browser history/bookmark GTK address-bar integration are backlog items.
- Browser panes are source-only and experimental in this alpha; use
--features browseronly when intentionally testing that path.
forktty doctoris the first stop: it explains config, session, socket, and hook config problems before they trigger a launch failure. Useforktty --json doctorfor socket, environment, executable, hook config, MCP config, and skill path diagnostics.- If terminal panes report
ghostty-gtk-embed.soas missing in a source tree, rungit submodule update --init vendor/ghosttyandscripts/ghostty-gtk-lib-probe.sh --ensure --print-path. For packaged builds, rebuild or reinstall withbash scripts/build-deb.shorbash scripts/build-appimage.sh; the packagers install the verified library intousr/lib. - If the GTK app refuses to start, run it from a terminal to see GLib/GTK error output, then re-run
forktty doctor. - The local socket lives at
$XDG_RUNTIME_DIR/forktty.sock(or/tmp/forktty-<uid>/forktty.sock). Stale or foreign sockets are refused on startup; remove them by hand only after confirming no other ForkTTY instance owns them. - A corrupt
~/.config/forktty/config.tomlor~/.local/state/forktty/session-v2.jsonis renamed aside as*.bad-<timestamp>so the app can start with defaults; the rename reason is logged to stderr. - Local logs live under
~/.local/share/forktty/logs/.
For usage questions and bug reports, start with SUPPORT.md
and include the output of forktty doctor, your distro/desktop
environment, install method, and the exact command or workflow that
failed. Security reports should follow SECURITY.md.
Useful commands:
cargo fmt --all --check
git submodule update --init vendor/ghostty
cargo run -p xtask -- check
scripts/ghostty-gtk-lib-probe.sh --ensure --print-path
cargo test --workspace --all-targets --no-default-features --features gtk-ghostty
cargo clippy --workspace --all-targets --no-default-features --features gtk-ghostty -- -D warnings
cargo build -p forktty-ui-gtk --no-default-features --features gtk-ghostty
scripts/gtk-ghostty-smoke.sh
cargo test -p forktty-ui-gtk --all-targets --no-default-features --features browser
desktop-file-validate packaging/linux/dev.forktty.forktty.desktop
bash scripts/build-deb.sh
bash scripts/build-appimage.shSee SPEC.md, ROADMAP.md, and docs/native-gtk-ghostty.md. Use docs/release-qa.md before tagging alpha releases.
Built from scratch for Linux, inspired by cmux and other multi-agent terminal workflows.
GNU Affero General Public License v3.0 (AGPL-3.0-only)
