Chapter 2: Quickstart and Workspaces

Getting productive with AutoDoc takes less than sixty seconds. Because AutoDoc treats your existing directory of Markdown files as canonical, initializing a workspace never alters, renames, or reformats your notes unless you explicitly save changes or invoke a document operation.

This chapter walks through installing the autodoc binary, launching your first interactive session, configuring multi-workspace policies (workspace.add), understanding how the watch-before-scan engine (core/follow) guarantees zero missed edits during Git branch switches, and inspecting live index health (index.status).


2.1 Installing AutoDoc

AutoDoc ships as a single binary containing the daemon (--serve), the terminal UI (--ui), the native GPU desktop interface (--gui, in the builds with the GUI: see The GUI Build), the headless RPC client (--call), and the offline HTML exporter (--export).

New to AutoDoc? The tutorials start here, a few minutes each; the Reference Book has the whole story.

Platform Install
Linux, macOS, with Go 1.26.9+, the GUI GOBIN=~/.local/bin go install -tags gui github.com/yongjohnlee80/autodoc/cmd/autodoc@latest
Any, with Go 1.26.9+, the TUI go install github.com/yongjohnlee80/autodoc/cmd/autodoc@latest
macOS, Linux mise: mise use -g github:yongjohnlee80/autodoc (the GUI build)
Linux, a server or container mise, the TUI build: mise use -g 'github:yongjohnlee80/autodoc[asset_pattern=autodoc-*-linux-amd64-tui.tar.gz]' (autodoc-*-linux-arm64-tui.tar.gz on arm64)
macOS, Linux Homebrew: brew install yongjohnlee80/tap/autodoc (on Linux, the TUI build)
Linux, macOS A release archive (amd64 and arm64, with SHA-256 checksums)
Windows Use WSL2 and any Linux method. A native Windows build is not published yet.

Use autodoc --gui to open the native GUI. It shares the same daemon and store as autodoc --ui and autodoc --call. --gui is the flag you run it with; -tags gui is the build tag that builds the GUI into the binary. On Linux, mise and the plain release archive are the GUI build, and so is go install -tags gui; the -tui archive, the Linux Homebrew formula and a go install without -tags gui are the TUI without GUI support.

The enterprise search server (autodoc --server, docs/enterprise) is in its own build: a linux-<arch>-server release archive, make build-server, or -tags server. The other builds leave it out, and say so when asked for --server.

Package Managers (brew and mise)

Each installs the one autodoc binary. Homebrew and mise install the release's binaries, and Homebrew pins each to the SHA-256 the release published: nothing is compiled on your machine, so neither needs Xcode. Start it with autodoc --ui: the TUI starts the daemon when nothing answers.

Which build mise installs. mise picks the release archive that matches the machine:

Building from Source (go install)

If you have Go 1.26.9 or newer installed, you can compile AutoDoc directly from source. go install builds from the tagged source, and autodoc --version then reports the module's version, as a release binary does.

# The terminal UI
go install github.com/yongjohnlee80/autodoc/cmd/autodoc@latest

# Verify binary version and supported features
autodoc --version

go install on macOS compiles with cgo, because the daemon watches workspace roots with the system's FSEvents: it needs the Command Line Tools (xcode-select --install; not the whole of Xcode). Without them the build stops at xcrun: error: invalid active developer path. Either install them, or build without cgo — CGO_ENABLED=0 go install … — and the daemon polls the roots instead of watching them: it works, but a change is noticed only after a scan of the root. On Linux, the TUI's go install needs no C compiler (the daemon uses inotify, and the store is pure Go).

The GUI with go install needs -tags gui, on every platform: without it, go install builds the TUI alone, and autodoc --gui says so. With it, the build uses cgo, so it needs a C compiler and, on Linux, the window system's development libraries (The GUI lists them):

GOBIN=~/.local/bin go install -tags gui github.com/yongjohnlee80/autodoc/cmd/autodoc@latest

GOBIN puts the binary in ~/.local/bin (keep it on your PATH) rather than in $(go env GOPATH)/bin.

Building from a Checkout (make)

Go 1.26.9. make build links no cgo: the daemon and the TUI. make build-gui adds the GUI (--gui, golib's gui on Gio), the one part that needs cgo; on macOS make build is make build-gui.

git clone https://github.com/yongjohnlee80/autodoc && cd autodoc && make build   # → bin/autodoc

make build stamps the version from git describe. The tests and the checks to run are in CONTRIBUTING.md. Before sending a change, read CONTRIBUTING.md.

The GUI Build (cgo)

The GUI needs cgo, and it is the only part that does:

A build without the GUI says so when asked for --gui, and how to get one.

Tip

On Linux, the GUI build links libEGL, libwayland-*, libxkbcommon(-x11), libX11(-xcb), libXcursor and libXfixes, so it does not start without them, even for autodoc --ui or autodoc --call. On a headless server or in a container, install the -tui build (or make build): it is static and needs no display libraries.


2.2 First Launch: autodoc --ui and autodoc --gui

AutoDoc uses explicit mode flags (--ui for the terminal interface, --gui for the native Gio desktop window). To register your first workspace and open it immediately:

# Register a workspace named "engineering-kb" (starts the daemon automatically if needed)
autodoc --call workspace.add '["engineering-kb", "/home/me/Notes/engineering-kb", ["**/*.md"], [".git/**"]]'

# Open the workspace in the terminal TUI (or omit the name to open the last-used workspace)
autodoc --ui engineering-kb

When you run autodoc --call workspace.add followed by autodoc --ui engineering-kb (or add the workspace interactively inside autodoc --ui with Go › Manage workspaces…), four things happen automatically:

  1. Daemon Bootstrap: If autodoc --serve is not yet running, the client starts it in the background and connects over the 0600 Unix domain socket.
  2. Store-Backed Workspace Registration: AutoDoc persists the workspace definition (engineering-kb) inside its SQLite store (autodoc.db) with your include and exclude globs.
  3. Watch-Before-Scan Indexing: The daemon attaches filesystem watches, scans all matching .md files and .todo-list/ tasks, extracts YAML frontmatter, splits prose into heading-aligned chunks, and populates the SQLite FTS5 index.
  4. Interactive Workspace Open: The terminal TUI opens on the page: the 120-column page editor, centred. The Files, Outline and Relations panels come when they are asked for (Chapter 3).

To launch the native desktop window instead of the terminal TUI, pass --gui:

autodoc --gui engineering-kb

Choosing the Workspace, and Recording Input (--debug)

autodoc --ui                # attaches to the daemon (starting it when nothing answers), in the last workspace used
autodoc --ui kb             # in workspace kb; a name the daemon does not have is refused, with the names it has
autodoc --ui --debug         # also record the event stream to autodoc-debug-<date>.jsonl, for an input or IME bug report
autodoc --ui --debug=r.jsonl # the same, to r.jsonl

--debug is --ui and --gui's: without one of them it is refused. It records the input and event stream to one JSONL file — the window's platform events and IME state when there is a window, the TUI's own trace in both modes — in the order everything happened, so one file reads platform → IME → TUI. The file contains everything typed, passwords included: it is written only where the flag says, never anywhere by default. How to record, and how a recording becomes the test that reproduces a bug on any machine, is in docs/filing-input-and-ime-bugs.md.


2.3 Registering and Configuring Workspaces

Workspaces in AutoDoc are stored inside the SQLite database (autodoc.db) rather than in config.toml, allowing you to add, rename, reconfigure and delete workspaces live from the TUI (Go › Manage workspaces…), the GUI, Neovim, or the CLI (workspace.add and workspace.configure) without restarting the daemon.

What a Workspace Is: a Name, a Root and Patterns

A workspace has a name (what --ui and every API call take), a root directory, and include and exclude patterns. New workspaces include **/*.md, **/*.txt, **/*.yaml, and **/*.yml, and exclude .git/** and **/node_modules/** by default. An excluded directory is never walked or watched. A workspace added before node_modules joined the defaults, still with .git/** alone, moves to the current default when the daemon starts; a workspace whose patterns were chosen keeps them. Patterns are root-relative globs: each /-separated segment is a path.Match pattern, and ** matches any number of whole segments. Bounded brace alternatives such as **/*.{md,txt} are expanded before matching. Existing workspaces keep their stored include patterns.

Registering and Configuring a Workspace via CLI (workspace.add and workspace.configure)

workspace.add takes up to four positional arguments [name, root, include?, exclude?], and workspace.configure updates any combination of workspace settings atomically in a single map:

# 1. Register the GlobalKB workspace with initial include/exclude globs
autodoc --call workspace.add '[
  "GlobalKB",
  "/home/me/kb",
  ["**/*.md"],
  [".git/**", "archive/raw-dumps/**"]
]'

# 2. Configure retrieval, schema, and embedding policies atomically
autodoc --call workspace.configure '[
  "GlobalKB",
  {
    "schema": "_schema/frontmatter.yaml",
    "section_tokens": 400,
    "abstract_chunk": true,
    "demote_superseded": true,
    "embedding_policy": "always",
    "todo_search": true,
    "graph_only": ["archive/**"]
  }
]'

A Document's Resources

A design doc often shows files beside it, as ![flow](plan/flow.mmd): diagrams, tables, pages. Include the ones that hold text, and they are searched with it and tied to it:

**/*.md   **/*.mmd   **/*.csv   **/*.html

The Manage Workspaces Dialog (Go › Manage workspaces…)

Go › Manage workspaces… lists the workspaces by title and directory on the left, and the settings of the one under the cursor on the right. Its settings are one dialog with two tabs, opened by Add…, Edit… or Advanced…; Ctrl+PageUp and Ctrl+PageDown switch tabs, and Enter in a field saves:

Save sends what changed in one workspace.configure, which saves all of it or none: a refused setting leaves every setting as it was, and the dialog opens again as it was typed with the reason.

Daemon Configuration (config.toml)

Because workspaces and sealed provider keys live in the SQLite store (core/config/config.go explicitly rejects unknown keys or legacy [[workspace]] / [embedding] sections so typos are caught at startup), ~/.config/autodoc/config.toml is reserved strictly for daemon-level [server], [follow], and [embedding_queue] settings:

# $XDG_CONFIG_HOME/autodoc/config.toml
[server]
socket = ""                 # default: $XDG_RUNTIME_DIR/autodoc.sock
state_dir = ""              # default: $XDG_STATE_HOME/autodoc (the daemon's log)
data_dir = ""               # default: $XDG_DATA_HOME/autodoc (the store, autodoc.db)

[follow]
poll_interval = "2s"        # the listing interval when a root cannot be watched

[embedding_queue]
max_inflight = 1            # requests to the shared provider, 1 or 2

A missing file is every default.

The embedding providers are kept in the store too, their API keys sealed, and added and chosen in the TUI's Search models dialog (see Semantic search).

SettingTypeDefaultDescription
namestring(Required)Unique identifier for the workspace used in RPC calls and UI workspace switchers (e.g. GlobalKB or EngineeringDocs)
rootstring(Required)Canonical absolute filesystem path to the workspace root directory
include[]string["**/*.md"]Glob patterns of files to index and watch relative to the workspace root
exclude[]string[".git/**","node_modules/**","vendor/**"]Glob patterns excluded from document indexing and directory watching
graph_only[]string[]Glob patterns of paths indexed for the link and relation graph only, never chunked for full-text or vector search (ADR 1792015212)
section_tokensint0 (default 400)Target maximum token budget per heading-bounded section chunk before splitting at paragraph boundaries (0 uses the default)
abstract_chunkboolfalseWhen true, synthesizes a dedicated chunk 0 for each document from its frontmatter abstract
demote_supersededboolfalseWhen true, ranks hits from a superseded document below its active successor
embedding_policystring"always"Controls vector generation: always (embed automatically), when opened (embed when the workspace is opened), or never (lexical only)
todo_searchbooltrueWhen true (default), includes the workspace's todo tasks in search results; false keeps them graph-only
schemastring"_schema/frontmatter.yaml"Path under the workspace root (or absolute) to the YAML frontmatter validation schema enforced by doc.validate ("" removes it)

Deep Dive into Key Workspace Policies


2.4 The Watch-Before-Scan Follower (core/follow)

A classic subtle bug in file-watching indexers is the scan-to-watch race window: if an indexer scans a directory tree first and attaches fsnotify / inotify watches afterward, any file modified by git checkout, git rebase, or an AI agent inside already-scanned subdirectories before the watcher attaches is silently missed until the next full restart.

AutoDoc prevents this race in core/follow by enforcing a strict Watch-Before-Scan state machine whenever a workspace boots or a new subdirectory appears.

The files change under AutoDoc at any time: an editor, git, an agent. core/follow makes the index converge on the root's content, and treats every event as a hint that names a path:

sequenceDiagram
    participant CLI as User / CLI (workspace.add)
    participant Daemon as autodoc --serve
    participant Watcher as fsnotify Watcher (core/follow)
    participant Scanner as Tree Walker & Parser
    participant Store as SQLite WAL Store (core/store)
    participant Embed as Embedding Worker Pool

    CLI->>Daemon: workspace.add {name, root, include, exclude}
    Daemon->>Watcher: 1. Attach recursive fsnotify watches BEFORE scan
    Watcher-->>Daemon: Watch descriptors active (buffering events)
    Daemon->>Scanner: 2. Walk directory tree (honoring exclude globs)
    loop Every matching .md file
        Scanner->>Scanner: Compute SHA-256, parse YAML frontmatter & headings
        Scanner->>Scanner: Split into section chunks (target section_tokens)
    end
    Scanner->>Store: 3. Commit lexical chunks, FTS5 tokens & wikilinks (batch tx)
    Daemon->>Watcher: 4. Drain buffered watch events (coalesce by path + mtime/hash)
    Watcher->>Store: Apply any concurrent edits that occurred during scan
    Daemon-->>CLI: Workspace ready for lexical & graph queries
    Daemon->>Embed: 5. Enqueue changed chunk hashes (if embedding_policy != off)
    Embed->>Store: Stream vector embeddings asynchronously in background

How Watch-Before-Scan Converges Without Gaps

  1. Attach Watches First: Before reading a single Markdown file's contents, core/follow walks the directory hierarchy to register recursive OS watch descriptors (inotify on Linux, FSEvents on macOS) and begins buffering incoming filesystem events in memory.
  2. Baseline Snapshot Scan: While filesystem events accumulate in the buffer, the scanner walks the tree, compares each file against the SQLite store, and hands any added or changed file to the indexer.
  3. Coalesced Buffer Drain: Once the baseline scan finishes, core/follow drains the buffered watch events. Multiple rapid events on the same path (such as Neovim writing a temp file and executing rename(2) over the target .md file) are coalesced by path.
  4. Version Idempotency: Each coalesced path is reconciled against the store. If the baseline scan already indexed that exact file version, the buffered event is a no-op; if a concurrent edit landed after the scanner visited that path, the follower hands the path to the indexer again, which indexes the newer version.

The follower never writes the index. It hands paths to the indexer, the store's one writer, which decides from each file's current state whether to delete, skip or re-index it.

Illustrative Scaling Profile: Full Tree Scan vs Single-File Watch Update01002003004005006007008009001,000Wall-Clock Time (ms)100500100025005000Workspace Corpus Size (Markdown Files)Indexing OperationIncremental Watch UpdateInitial Scan (Lexical)

As the illustrative scaling curve above shows, initial workspace tree scanning scales linearly ($O(N)$) with the number of files in the repository, whereas incremental core/follow updates remain $O(1)$—re-chunking and committing only the single modified file regardless of total workspace size.


2.5 Inspecting Index and Watcher Health (index.status)

You can verify that your workspace is synchronized, inspect document and chunk counts, check frontmatter diagnostics, and confirm whether background vector embeddings have caught up at any time using index.status:

autodoc --call index.status '["GlobalKB"]'

The daemon returns the statusJSON snapshot (rpc/index.go):

{
  "cursor": 1842,
  "oldest_retained": 1,
  "docs": 618,
  "chunks": 3492,
  "pending_jobs": 0,
  "unparsed_frontmatter": [],
  "diagnosed": 0,
  "failing": [],
  "held": 0,
  "held_stale": 0,
  "held_unchecked": 0,
  "following": {
    "mode": "watching",
    "error": "",
    "retrying": []
  },
  "embeddings": {
    "provider": "local-qwen-0.6b",
    "model": "ollama:qwen3-embedding:0.6b:1024",
    "target": "ollama:qwen3-embedding:0.6b:1024",
    "texts": 3492,
    "pending": 0,
    "semantic": "ready",
    "last_error": "",
    "refused": 0,
    "target_pending": 0,
    "target_refused": 0,
    "refused_texts": []
  }
}

Re-Indexing a Specific File (index.reindex)

If you ever want to force the daemon to re-parse and re-index a specific document immediately via RPC, call index.reindex with the workspace name and workspace-relative path:

autodoc --call index.reindex '["GlobalKB", "adrs/0042-sqlite-wal-lease.md"]'

And if you adjust workspace-wide chunking or glob policies via workspace.section_size or workspace.set_patterns, the daemon automatically reconciles the affected files in the background while continuing to serve read queries from the SQLite WAL snapshot.


2.6 Multi-Workspace Workflows and Scoped Queries

Engineers frequently work across multiple knowledge scopes at once: a personal or team-wide GlobalKB containing cross-cutting conventions and ADRs, alongside a repository-local MonorepoDocs workspace checked into the current Git worktree.

AutoDoc's daemon tracks an arbitrary number of simultaneous workspaces in a single autodoc --serve process:

# List all registered workspaces and their current settings
autodoc --call workspace.list '[]'

# Update an existing workspace's include and exclude globs
autodoc --call workspace.set_patterns '[
  "MonorepoDocs",
  ["adrs/**/*.md", "runbooks/**/*.md", "rfcs/**/*.md"],
  [".git/**", "vendor/**", "dist/**"]
]'

# Update an existing workspace's section token budget and embedding policy
autodoc --call workspace.configure '[
  "MonorepoDocs",
  {
    "section_tokens": 512,
    "embedding_policy": "when opened"
  }
]'

# Detach a temporary workspace without touching any Markdown files on disk
autodoc --call workspace.remove '["ScratchNotes"]'

Environment Variables for Agent and Editor Integration

When running inside automated environments (such as auto-agents.nvim, embedded TerminalTabs, or CI runners), convention files and shell scripts commonly use standard environment variables to pass workspace context:


← Previous: Chapter 01: Architecture & Surfaces | Index | Next: Chapter 03: Modal Editor & Panels →