this document describes the internal architecture of fshell (fsh) — workspace crate structure, execution model, memory layout, and runtime subsystems.
- overview
- workspace map & dependency graph
- binary & startup lifecycle
- core data model & memory layout
- environment state (
Env) - pipeline execution & async dataflow
- posix polyglot integration (
fshell-posix) - crate deep dives
- security & sandboxing
- performance architecture
fshell is built as a modular rust workspace (edition 2024, unix-only for macOS and Linux).
instead of treating all data as untyped byte streams, fshell routes structured Arc<Val> payloads across asynchronous tokio channels while maintaining zero-friction compatibility with unix external binaries and posix scripts.
key architecture traits:
- single binary: the root package builds one binary (
fsh). symlinked utility invocations (e.g.ls) trigger multicall dispatch without launching the full REPL. - in-process polyglot engine:
.fshscripts, inline POSIX blocks (sh { ... }), and POSIX shell scripts run against the same liveEnvstate without spawning intermediate subprocesses. - fast data structures:
Ustrinterned string keys eliminate string hashing and allocation on field lookups;FxIndexMapmaintains record insertion order.
the workspace is organized into 12 crates with strict dependency layering:
┌───────────────────────────────────────────────────────────────────┐
│ fshell (fsh) │
└─────────────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────┘
│
┌─────▼───────────┐
│ fshell-repl │
└─────┬───────────┘
│
┌─────▼───────────┐ ┌───────────────────┐ ┌──────────────────┐
│ fshell-bridge │ │ fshell-builtins │ │ fshell-posix │
└─────┬───────────┘ └────────┬──────────┘ └─────────┬────────┘
│ │ │
├───────────────────────────┼───────────────────────────┤
│ │ │
┌─────▼───────────┐ ┌────────▼──────────┐ ┌─────────▼────────┐
│ fshell-engine │◄─────┤ fshell-render │ │ fshell-sandbox │
└─────┬───────────┘ └───────────────────┘ └──────────────────┘
│
├───────────────────────────┬───────────────────────────┐
│ │ │
┌─────▼───────────┐ ┌────────▼──────────┐ ┌─────────▼────────┐
│ fshell-core │ │ fshell-ls │ │ fshell-git │
└─────┬───────────┘ └────────┬──────────┘ └──────────────────┘
│ │
│ ┌────────▼──────────┐
│ │ fshell-capabilities│
│ └───────────────────┘
│
┌─────▼───────────┐
│ fshell-hash │
└─────────────────┘
| Crate | Responsibility | Key Dependencies |
|---|---|---|
fshell-core |
AST definitions, parser, Val dynamic type system, Theme, diagnostics |
ustr, indexmap, chrono, fshell-hash |
fshell-capabilities |
capability token issuance, capability set verification, security state | fshell-core |
fshell-hash |
sponge hash algorithms, fast hashing helpers | fshell-core |
fshell-render |
miette-based graphical / compact / json error renderers | fshell-core |
fshell-sandbox |
landlock (linux) and SBPL (macOS) subprocess sandbox hooks | fshell-core, fshell-engine |
fshell-git |
git metadata extraction and status detection | fshell-core |
fshell-ls |
directory listing, metadata formatting, icons, tree layout | fshell-core, fshell-git, fshell-hash |
fshell-engine |
evaluator, pipeline executor, reactive streams, Env |
fshell-core, fshell-capabilities, fshell-hash |
fshell-builtins |
~117 built-in commands registered into Env |
fshell-core, fshell-engine, fshell-ls, fshell-capabilities |
fshell-bridge |
external command fallback, globbing, path caching | fshell-core, fshell-engine, fshell-capabilities |
fshell-posix |
POSIX/Bash syntax parser and runtime engine | fshell-core, fshell-engine |
fshell-tty |
Unix input decoding, raw mode and ANSI primitives, cursor and size queries, lifecycle guards | libc, futures, thiserror |
fshell-terminal |
ratatui backend, scoped terminal sessions, TUI runner | fshell-tty, ratatui |
fshell-repl |
interactive prompt, FTUI, native line-editor, completions, config TUI, SQLite history | fshell-engine, fshell-builtins, fshell-bridge, fshell-terminal, ratatui |
src/main.rs builds a single-threaded tokio runtime (Builder::new_current_thread().enable_all()) and enters fshell::run().
before initializing the shell, argv[0] is inspected for multicall routing:
- if argv[0] is
ls(via symlink or direct exec),fshell::run_utility()runs the standalone utility and terminates immediately without allocating the engine or REPL. - otherwise, the process continues into CLI argument parsing (
src/lib.rs).
every session initializes components in strict bottom-up dependency order:
fshell_core::init(): registers core diagnostics and error formatting hooks.fshell_capabilities::init(): prepares the security capability subsystem.Env::new()orEnv::for_command(): creates the central runtime environment.fshell_builtins::init(&env): registers all active builtin command handlers.fshell_bridge::init(&env): registers external command fallback dispatch and glob expansion.init_posix_handler(): registers thefshell-posixexecution callback intofshell-engine.- login & profile loading: loads host login environment and evaluates startup profiles (
init.fsh/config.toml). - path cache warmup: runs
warmup_path_cache()in the background to build the binary lookup index. - REPL or execution:
fsh -c <cmd>: executes the inline command and exits.fsh <script.fsh>: executes the file non-interactively.fsh: startsfshell_repl::init(&env)and launches the interactive FTUI.
-cpath (Env::for_command()): lightweight boot omitting session handoff, reactive stream schedulers, history database open, and FTUI terminal raw mode.- interactive path (
Env::new()): full boot with reactive schedulers, SQLite history (history.db), frecency tracking (frecency.db), prompt segment renderers, and signal traps.
fshell implements seamless runtime reloading (reload --full) via fshell_engine::handoff.
the running session serializes its live state to JSON:
- global and local variables
- defined user functions
- capability tokens
- reactive pipeline definitions
- shell options and cwd
the new binary is launched with --handoff <path>, which deserializes the state into the fresh Env before the user interacts with the prompt.
the universal data currency across all fshell crates is Val (crates/fshell-core/src/val.rs):
pub enum Val {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
List(Vec<Val>),
Map(FxIndexMap<Ustr, Val>),
DateTime(chrono::DateTime<chrono::Utc>),
Blob(Vec<u8>),
ObjectGraph {
root: NodeId,
graph: Arc<GraphStorage>,
},
Capability(ResourceHandle),
ReactiveStream(tokio::sync::watch::Receiver<Vec<Val>>),
}Val::Map uses FxIndexMap<Ustr, Val>.
Ustr is an interned string handle representing an immutable string in a global string pool. looking up a field like item.status or record.cpu performs an integer/pointer comparison ($O(1)$) rather than a string hash and equality loop across thousands of items.
Val::ObjectGraph stores directed property graphs:
pub struct GraphStorage {
pub nodes: FxHashMap<NodeId, NodeData>,
pub edges: FxHashMap<NodeId, Vec<EdgeData>>,
}graph nodes and edges store property dictionaries (FxIndexMap<Ustr, Val>). equality checks between two graphs execute an Arc::ptr_eq fast path before performing structural comparisons.
security privileges are represented by ResourceHandle variants:
pub enum ResourceHandle {
ReadDir(PathBuf),
WriteDir(PathBuf),
ReadFile(PathBuf),
WriteFile(PathBuf),
NetworkSocket(String),
NetworkAll,
ReadEnv(String),
WriteEnv(String),
ProcessSpawn,
ProcessSpawnPath(String),
}the central Env struct in crates/fshell-engine/src/lib.rs delegates state across modular sub-structures:
pub struct Env {
pub vars: Arc<RwLock<FxHashMap<String, Val>>>,
pub local_vars: Option<Arc<LocalScope>>,
pub fns: Arc<RwLock<FxHashMap<String, (Vec<Param>, Option<String>, Vec<Stmt>)>>>,
pub aliases: Arc<RwLock<FxHashMap<String, String>>>,
pub builtins: Arc<RwLock<FxHashMap<String, BuiltinHandler>>>,
pub caps: Arc<CapsSubsystem>,
pub hooks: Arc<HooksSubsystem>,
pub reactive: Arc<ReactiveSubsystem>,
pub prompt: Arc<PromptSubsystem>,
pub job_control: Arc<JobControlSubsystem>,
pub options: Arc<RwLock<ShellOptions>>,
pub profiler: Arc<RwLock<ProfilerState>>,
pub ast_cache: Arc<RwLock<AstCache>>,
}all internal synchronization uses parking_lot::RwLock (re-exported via fshell_core::RwLock).
-
env.push_scope(locals): creates a cloned childEnvwhoselocal_varsis a newLocalScopeframe linked to the current one. lookups walk outward through the chain (so loop bodies andfilter/mapstages still see function parameters) and then fall through to the environment; writes update the innermost frame that already holds the name, so mutating a parameter inside a block stays visible after it. -
built-in variables: special shell variables (
$?,$#,$@,$*,$0..$9) are resolved dynamically during variable lookup.
to guarantee deadlock freedom across concurrent pipeline stages and background tasks, the engine enforces a strict lock acquisition hierarchy (docs/LOCK-ORDERING.md):
in debug builds, acquiring locks out of order panics immediately via runtime instrumentation macros (lock_caps!, lock_vars!, lock_fns!, etc.). locks are never held across .await yield points or subprocess spawns.
pipelines execute asynchronously. each stage runs in its own tokio task, communicating with downstream consumers via bounded mpsc channels (crates/fshell-engine/src/pipeline.rs):
pub enum PipelinePayload {
Data(Arc<Val>),
Bytes(Vec<u8>),
Structured(FshDiag),
}- structured data is passed as
PipelinePayload::Data(Arc<Val>), avoiding deep memory clones between stages. - byte streams from external binaries are converted into line-buffered
Val::Stringitems or forwarded directly to stdout.
when executing a command stage in a pipeline, resolution follows this order:
- alias expansion: expands registered aliases unless shadowed by built-in keywords.
- user-defined function: executes matching
fndefinitions in a scoped local frame. - built-in command: invokes the matching in-process
BuiltinHandler. - fallback handler (
fshell-bridge): searches system$PATHvia the path cache, checks capabilities, applies sandboxing profiles, and spawns the external process.
pipeline keywords (filter, map, sort, grep, mark, count, limit, traverse, hash) run as native streaming transformers inside the engine.
when a keyword is followed by a command-line flag (sort -u), the parser escapes keyword handling and delegates the call to external binary execution.
when a pipeline connects to an external command or terminal output, boundary operators serialize the Val stream into bytes:
@json: writes JSON arrays or line-delimited JSON.@yaml: serializes YAML documents.@msgpack: writes binary MessagePack streams.@csv: emits comma-separated rows.@table: formats records as Unicode/ASCII terminal tables.@bar: renders terminal horizontal distribution charts.
fshell-posix provides a full POSIX-compliant parser and evaluator. it does not invoke /bin/sh or /bin/bash.
instead, the POSIX abstract syntax tree executes against the same fshell_engine::Env:
- POSIX shell variables read and write to
env.vars. - POSIX functions register into
env.fns. - POSIX pipelines reuse
execute_pipelineandfshell-bridge.
the shell seamlessly switches execution modes:
- shebang detection:
source script.shautomatically routes tofshell-posixif the file begins with#!/bin/shor#!/bin/bash. - inline blocks:
sh { ... }blocks parse the inner string as POSIX source and evaluate it in-process. - CLI flag:
fsh --posix script.shevaluates the entire file in POSIX mode.
contains no dependencies on other workspace crates. defines the AST grammar (Expr, Stmt, PipelineStage), the Parser implementation, the Val enum, and the unified error system (ShellError { code: ErrorCode, message, span, help }, FshDiag, ParseError) with the ErrorCode taxonomy (FSH-*-###), and common hashing aliases (FxIndexMap).
maintains capability tokens, tracks permission grants, and verifies file path prefixes and socket rules before operations run.
provides the custom fhash sponge digest, XOF, KDF, and MAC APIs, plus a deterministic non-cryptographic hasher. The workspace's historical Fx* map aliases use Rust's randomly seeded RandomState to reduce hash-flooding risk; callers can still select the deterministic hasher explicitly for trusted keys.
the runtime engine. implements eval_stmt/eval_expr returning Result<Flow, ShellError> where Flow { Normal, ConditionFalse, Break, Continue, Return(Val), Exit(i32) } separates control flow from errors, pipeline orchestration via PipelineFailure { ConditionFalse, Hard(FshDiag) }, reactive watch cells, session handoffs, AST caching, and execution profiling.
registers built-in shell utilities (cd, pwd, cp, mv, rm, ps, df, kill, curl, jq, sql, vault, etc.). feature-gated to allow building lightweight or minimal distributions.
handles external subprocess execution, process IO pipes, argument globbing, and the "did you mean?" command suggestion engine.
contains the POSIX grammar lexer, parser, and interpreter that operates directly on fshell_engine::Env.
high-performance file listing library. inspects filesystem metadata, queries fshell-git for repo status, formats human-readable file sizes, and renders tree or grid layouts.
fast git repository inspector. reads HEAD, refs, stashes, and dirty worktree states to populate prompt status segments and ls metadata columns.
miette-based diagnostic rendering engine. converts engine and parser errors into graphical terminal snippets with source spans, compact single-line messages, or JSON error objects.
implements subprocess sandboxing via OS-level security primitives:
- Linux: Landlock security rules and namespace unsharing.
- macOS: SBPL (Seatbelt) sandbox profiles applied in subprocess
pre_execfork hooks.
the interactive terminal frontend. integrates Reedline for line editing, FTUI for TUI rendering, interactive configuration visual editor (config tui), prompt customizer studio, SQLite history storage, fuzzy completion menus, and real-time syntax highlighting.
fshell enforces two independent layers of security:
- capability-based authorization: scripts running in
--strictmode cannot access the filesystem or network unless wrapped in explicitwith caps(...) { ... }blocks. - subprocess sandboxing (
fshell-sandbox): external commands can be restricted to designated read/write directories and denied network access via OS kernels (Landlock/SBPL). - destructive command confirmation: dangerous commands (
rm -rf /, formatting root devices, raw block writes) trigger an interactive confirmation prompt before dispatching.
-
$O(1)$ property access:Ustrstring interning ensures map lookups avoid repeated string hashing. -
zero-copy streaming:
Arc<Val>payloads move across tokio channel buffers without cloning underlying heap structures. -
AST caching: sourced scripts cache parsed AST representations keyed by path mtime and content hash (
AstCache), avoiding repeated lexical parsing. -
release compilation profile: the release binary compiles with fat LTO,
codegen-units = 1, stripped symbols, andpanic = "abort"for minimal startup latency and optimal cache locality.