Introduction

Decknix is an opinionated Nix framework for macOS configuration management. It combines Nix Flakes, nix-darwin, and home-manager into a batteries-included system that's easy to customise and share across teams.

Why Decknix?

  • One command to set up a Mac — bootstrap installs Nix, nix-darwin, and your full dev environment
  • Layered configuration — framework defaults → org/team configs → personal overrides
  • Everything in Nix — editors, shell, git, window manager, CLI tools, AI tooling
  • Team-friendly — org configs are versioned flake inputs that everyone shares
  • Easy to override — every default uses lib.mkDefault, so your preferences always win

What's Included

CategoryHighlights
EditorsEmacs (full IDE with 13+ modules), Vim
ShellZsh with Starship prompt, completions, syntax highlighting
GitDelta diffs, Magit, Forge (GitHub PRs from Emacs)
Dev Toolsripgrep, jq, curl, gh CLI, language servers
Window ManagerAeroSpace tiling WM with fuzzy workspace picker
AI ToolingAugment Code agent with declarative MCP config
CLIdecknix switch, decknix update, extensible subcommands

How This Documentation Is Organised

  • Getting Started — install and build your first configuration
  • Architecture — understand the 3-layer model, config loader, and directory layout
  • Configuration — customise settings, features, secrets, and org configs
  • Modules — explore every module: editors, shell, git, WM, AI
  • CLI Reference — core commands and the extension system
  • Guides — set up org configs for your team, develop the framework, troubleshoot

Installation

This guide walks you through setting up decknix on a fresh or existing macOS system.

Prerequisites

  • macOS (Apple Silicon or Intel)
  • Administrator access (for initial Nix installation)
  • ~10GB disk space for the Nix store

Run the bootstrap script to install everything from scratch:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/ldeck/decknix/main/bin/bootstrap)"

This will:

  1. Install Nix with flakes enabled
  2. Install nix-darwin
  3. Create your local config directory at ~/.config/decknix/
  4. Initialize a flake in ~/.config/decknix/
  5. Prompt you for username and hostname

Option 2: Existing Nix Installation

If you already have Nix with flakes enabled:

# Create and enter your config directory
mkdir -p ~/.config/decknix && cd ~/.config/decknix

# Initialize from template
nix flake init -t github:ldeck/decknix

# Edit your settings
$EDITOR settings.nix

Edit settings.nix with your machine details:

{
  username = "your-username";     # macOS username
  hostname = "your-hostname";     # Machine name
  system   = "aarch64-darwin";    # or "x86_64-darwin" for Intel
  role     = "developer";         # "developer", "designer", or "minimal"
}

Then build and switch:

decknix switch
# Or if decknix CLI isn't installed yet:
sudo darwin-rebuild switch --flake .#default --impure

For Organisation Members

If your team maintains an org config repo, check their README for a dedicated bootstrap script that sets up both decknix and the team configuration in one step.

See Organisation Configs for how org configs work.

Next Steps

First Configuration

After installation, you'll have this directory structure:

~/.config/decknix/
├── flake.nix           # Main flake (imports decknix)
├── flake.lock          # Locked dependencies
├── settings.nix        # username, hostname, system, role
└── local/              # Your personal overrides
    ├── home.nix        # Home-manager config
    └── system.nix      # Darwin system config

Set Up Git Identity

Edit ~/.config/decknix/local/home.nix:

{ pkgs, ... }: {
  programs.git.settings = {
    user.email = "you@example.com";
    user.name = "Your Name";
  };
}

Add Your Packages

{ pkgs, ... }: {
  home.packages = with pkgs; [
    nodejs
    python3
    go
  ];
}

Choose Your Editor Profile

Decknix offers tiered editor profiles:

ProfileEmacs IncludesVim Includes
minimalCore, completion, editing, UI, undoBase config
standard+ development, magit, treemacs, languages, welcome+ whitespace, skim
full (default)+ LSP, org-mode, HTTP client, agent-shell
customYour own config (disables framework)Your own config

Change profiles in your home.nix:

{ ... }: {
  decknix.editors.emacs.profile = "standard";
  decknix.editors.vim.profile = "minimal";
}

Apply Your Changes

decknix switch

The first build takes a few minutes as it downloads packages. Subsequent builds are faster.

Verify Installation

# Check Emacs daemon is running
launchctl list | grep emacs

# Open a file in Emacs
ec test.txt

# Check Magit
# In Emacs: C-x g (opens git status)

Next Steps

Applying Changes

Day-to-Day Workflow

The core loop is: edit → switch → done.

Switch to Your New Configuration

decknix switch

This runs darwin-rebuild switch under the hood, applying both system and home-manager changes atomically.

Dry Run (Build Without Activating)

decknix switch --dry-run

Builds the configuration to check for errors without actually switching to it.

Update Framework and Dependencies

# Update all flake inputs (decknix, nixpkgs, etc.)
decknix update

# Update a specific input
decknix update decknix

After updating, run decknix switch to apply.

Test Local Framework or Org-Config Changes

Point decknix switch at a local checkout of any flake input using --override INPUT=PATH (repeatable):

# Test a local decknix checkout
decknix switch --override decknix=~/tools/decknix

# Test decknix and your org-config together
decknix switch \
  --override decknix=~/tools/decknix \
  --override nc-config=~/Code/my-org/decknix-config

Each --override becomes --override-input <INPUT> path:<PATH> on the underlying darwin-rebuild call.

If you use the same overrides every day, pin them in ~/.config/decknix/settings.toml so plain decknix switch picks them up automatically — see decknix switch → Persistent overrides.

Common Patterns

Edit Personal Config

$EDITOR ~/.config/decknix/local/home.nix
decknix switch

Add a New Package

# ~/.config/decknix/local/home.nix
{ pkgs, ... }: {
  home.packages = with pkgs; [
    kubectl
    helm
  ];
}
decknix switch

Disable a Module

# ~/.config/decknix/local/home.nix
{ ... }: {
  programs.emacs.decknix.welcome.enable = false;
  decknix.wm.aerospace.enable = false;
}
decknix switch

Troubleshooting

"command not found: decknix"

The CLI isn't in your path yet. Use the full command:

sudo darwin-rebuild switch --flake ~/.config/decknix#default --impure

Build Errors

Check the trace output to see which files were loaded:

[Loader] home + /Users/you/.config/decknix/local/home.nix
[Loader] system + /Users/you/.config/decknix/local/system.nix

Reset to Clean State

rm -rf ~/.config/decknix
# Re-run bootstrap or nix flake init

How Decknix Works

Decknix uses a 3-layer configuration model where each layer can override the one below it.

The 3 Layers

┌─────────────────────────────────────────┐
│  Layer 3: Personal Overrides            │  ~/.config/decknix/local/
│  Your packages, identity, preferences   │  ~/.config/decknix/<org>/
├─────────────────────────────────────────┤
│  Layer 2: Organisation Configs          │  Flake inputs (versioned repos)
│  Team tools, standards, shared settings │  e.g. inputs.my-org-config
├─────────────────────────────────────────┤
│  Layer 1: Decknix Framework             │  github:ldeck/decknix
│  Sensible defaults for everything       │  darwinModules + homeModules
└─────────────────────────────────────────┘

Layer 1 — Framework

The decknix flake provides darwinModules.default and homeModules.default containing opinionated defaults for shell, editors, git, window management, and more. Every value uses lib.mkDefault, so it can be overridden by any higher layer without lib.mkForce.

Layer 2 — Organisation Configs

Teams create separate repos (e.g. github:MyOrg/decknix-config) that export their own darwinModules.default and homeModules.default. These are added as flake inputs and supply team-specific tools, packages, and settings.

Layer 3 — Personal Overrides

Each user's ~/.config/decknix/ directory contains personal overrides that are auto-discovered and merged at build time. These live outside git (or in a personal dotfiles repo) and let you customise without touching shared configs.

How Builds Work

When you run decknix switch:

  1. mkSystem reads your settings.nix (username, hostname, system, role)
  2. Constructs a darwinConfigurations.default that merges:
    • Framework modules (Layer 1)
    • Org modules passed via darwinModules / homeModules (Layer 2)
    • configLoader output from ~/.config/decknix/ (Layer 3)
  3. Calls darwin-rebuild switch to atomically activate the new generation

Key Design Decisions

  • lib.mkDefault everywhere — the framework never fights your preferences
  • Filesystem auto-discovery — drop a .nix file in the right place and it's loaded
  • Flake inputs for teams — version-pinned, reproducible, Renovate-watchable
  • Secrets separatedsecrets.nix files are gitignored and loaded alongside home.nix
  • Impure builds--impure is required so the config loader can read ~/.config/decknix/ at build time

Next

Directory Layout

User Configuration

~/.config/decknix/                   # Your flake + personal overrides
├── flake.nix                        # Main flake (imports decknix + org configs)
├── flake.lock                       # Pinned dependency versions
├── settings.nix                     # username, hostname, system, role
│
├── local/                           # Personal overrides (always loaded)
│   ├── home.nix                     # Packages, git identity, shell aliases
│   ├── system.nix                   # macOS system preferences
│   └── secrets.nix                  # Auth tokens, keys (gitignored)
│
├── <org-name>/                      # Per-org personal overrides
│   ├── home.nix                     # Org-specific personal tweaks
│   ├── system.nix
│   ├── secrets.nix
│   └── home/                        # Nested home modules (recursively loaded)
│       └── extra.nix
│
└── secrets.nix                      # Root-level secrets (also supported)

Key Points

  • local/ is for generic personal config — git identity, extra packages, shell aliases
  • <org-name>/ directories match flake input names — overrides specific to that org
  • secrets.nix files are gitignored and loaded alongside home.nix
  • home/ subdirectories are recursively scanned for additional .nix files
  • All directories are auto-discovered — no registration needed

Framework Source

decknix/
├── bin/                             # Bootstrap scripts
│   └── bootstrap.sh                 # Fresh install script
├── cli/                             # Rust CLI source
│   └── src/main.rs                  # switch, update, help, extensions
├── docs/                            # This documentation site
├── lib/
│   ├── default.nix                  # mkSystem + configLoader
│   └── find.nix                     # File discovery utilities
├── modules/
│   ├── cli/                         # decknix CLI nix-darwin module
│   │   └── default.nix              # Subtask system, extensions.json
│   ├── common/
│   │   └── unfree.nix               # Unfree package allowlist
│   ├── darwin/                      # macOS system modules
│   │   ├── default.nix              # System packages, fonts, defaults
│   │   ├── aerospace.nix            # AeroSpace tiling WM (system-level)
│   │   └── emacs.nix                # Emacs daemon service
│   └── home/                        # Home-manager modules
│       ├── default.nix              # Imports + default packages
│       ├── options.nix              # Role templates, core options
│       └── options/
│           ├── cli/                  # auggie, board, extensions, nix-github-auth
│           ├── editors/              # emacs/ (13 modules), vim/
│           └── wm/                   # aerospace/, hammerspoon/, spaces.nix
├── pkgs/                            # Custom Nix packages
├── templates/                       # Flake templates for `nix flake init`
└── flake.nix                        # Framework flake

Config Loader

The config loader (decknix.lib.configLoader) is the engine that discovers and merges personal override files from ~/.config/decknix/.

How It Works

  1. Scan ~/.config/decknix/ for all subdirectories
  2. For each directory, look for:
    • identity.nix — org user identity (auto-wired to config.<org>.user.*)
    • home.nix — home-manager module
    • system.nix — nix-darwin module
    • secrets.nix — secrets (merged into home-manager)
    • home/**/*.nix — recursively loaded home modules
  3. Also check for root-level files (~/.config/decknix/home.nix, etc.)
  4. Import every discovered file and trace what was loaded

Discovery Order

~/.config/decknix/
├── local/home.nix              ← loaded
├── local/system.nix            ← loaded
├── local/secrets.nix           ← loaded (merged into home)
├── nurturecloud/identity.nix   ← auto-wired to config.nurturecloud.user.*
├── nurturecloud/home.nix       ← loaded (can reference config.nurturecloud.user.*)
├── nurturecloud/system.nix     ← loaded
├── secrets.nix                 ← loaded (root-level)
└── home.nix                    ← loaded (root-level)

All files are merged — ordering within a layer is discovery order (alphabetical by directory name).

Identity Files

When <org>/identity.nix exists, the loader auto-generates NixOS module options under config.<org>.user.* and injects them into both darwin and home-manager module systems. This means any Nix module — org configs, personal overrides, or framework modules — can reference the identity without imports.

Creating an identity file

The file is a plain Nix attrset (not a module):

# ~/.config/decknix/nurturecloud/identity.nix
{
  email = "you@nurturecloud.com";
  name = "Your Name";
  githubUser = "your-github";
  gpgKey = "ABCDEF1234567890";   # optional — omit or leave empty
}

Using identity in modules

The directory name becomes the option namespace. For a directory named nurturecloud, the following options are available everywhere:

{ config, ... }: {
  # In any darwin or home-manager module:
  some.service.email = config.nurturecloud.user.email;
  some.service.name  = config.nurturecloud.user.name;
  # etc.
}

Available options

OptionTypeDescription
config.<org>.user.emailstrUser email for the organisation
config.<org>.user.namestrUser full name
config.<org>.user.githubUserstrGitHub username
config.<org>.user.gpgKeystrGPG signing key ID (empty if not set)

Multi-org support

Each org directory can have its own identity.nix with different values. A user working across two orgs might have:

~/.config/decknix/
├── nurturecloud/identity.nix   → config.nurturecloud.user.email = "me@nc.com"
└── sideproject/identity.nix    → config.sideproject.user.email = "me@sp.com"

The local/ directory is excluded from identity discovery — it's for personal overrides only.

Trace Output

When you build, the loader traces what it finds:

[Loader] identity + /Users/you/.config/decknix/nurturecloud/identity.nix
[Loader] system + /Users/you/.config/decknix/local/system.nix
[Loader] home + /Users/you/.config/decknix/local/home.nix
[Loader] No secrets modules found.

Use this to verify which files are being picked up:

decknix switch 2>&1 | grep "\[Loader\]"

API Reference

configLoader

decknix.lib.configLoader {
  lib       = nixpkgs.lib;       # Required
  username  = "your-username";   # Required
  hostname  = "your-hostname";   # Optional (default: "unknown")
  system    = "aarch64-darwin";  # Optional (default: "unknown")
  role      = "developer";       # Optional (default: "developer")
  homeDir   = "/Users/you";      # Optional (auto-derived from username + system)
  configDir = "/Users/you/.config/decknix";  # Optional (auto-derived)
}

Returns:

{
  modules = {
    home     = [ ... ];  # List of imported home + secrets modules
    system   = [ ... ];  # List of imported system modules
    identity = [ ... ];  # Auto-generated identity modules (config.<org>.user.*)
  };
  allDirs = [ "local" "nurturecloud" ];  # Discovered directory names
}

mkSystem

The top-level builder that wires everything together:

decknix.lib.mkSystem {
  inputs;                         # Your flake inputs (must include decknix, nixpkgs, nix-darwin)
  settings    = import ./settings.nix;  # { username, hostname, system, role }
  darwinModules = [ ... ];        # Extra darwin modules (org configs)
  homeModules   = [ ... ];        # Extra home-manager modules (org configs)
  extraSpecialArgs = {};          # Additional args passed to modules
  stateVersion = "24.05";        # home.stateVersion
}

Returns: { darwinConfigurations.default = ...; }

The build merges modules in this order:

  1. Framework darwinModules.default / homeModules.default
  2. Your darwinModules / homeModules args (org configs)
  3. configLoader identity modules (config..user.*)
  4. configLoader system/home modules (personal overrides from filesystem)

Settings Reference

The settings.nix file in your flake directory defines your machine identity:

# ~/.config/decknix/settings.nix
{
  username = "ldeck";            # Your macOS username
  hostname = "lds-mbp";         # Machine hostname
  system   = "aarch64-darwin";  # "aarch64-darwin" (Apple Silicon) or "x86_64-darwin" (Intel)
  role     = "developer";       # Bootstrap template: "developer", "designer", or "minimal"
}

Fields

FieldTypeDefaultDescription
usernamestring"setup-required"Your macOS login username (whoami)
hostnamestring"setup-required"Machine hostname (hostname -s)
systemstring"aarch64-darwin"Nix system identifier
roleenum"developer"Determines which bootstrap template is applied

Roles

The role field selects a starter template for first-time setup:

RoleWhat It Adds
developerGit config template + nodejs
designerInkscape
minimalNothing extra — blank slate

After the first build, the role has minimal impact. You can always add or remove packages in your home.nix regardless of role.

Where Settings Are Used

Settings flow into the build via mkSystem:

# flake.nix
outputs = inputs@{ decknix, ... }:
  decknix.lib.mkSystem {
    inherit inputs;
    settings = import ./settings.nix;
  };

mkSystem uses them to:

  • Set networking.hostName
  • Set system.primaryUser
  • Derive the home directory path
  • Pass role to home-manager for template selection
  • Configure configLoader paths

Personal Overrides

Personal overrides live in ~/.config/decknix/ and are auto-discovered by the config loader.

Directory Structure

~/.config/decknix/
├── local/                  # Generic personal config (always loaded)
│   ├── home.nix
│   ├── system.nix
│   └── secrets.nix         # Gitignored
├── my-org/                 # Per-org overrides (matches flake input name)
│   ├── home.nix
│   └── home/
│       └── kubernetes.nix  # Recursively loaded
└── secrets.nix             # Root-level secrets

How Overrides Work

Decknix framework defaults all use lib.mkDefault, so any value you set in your personal files wins automatically:

# Framework sets:   programs.git.settings.pull.rebase = lib.mkDefault true;
# Your override:    programs.git.settings.pull.rebase = false;  ← wins

For cases where another module explicitly sets a value (without mkDefault), use lib.mkForce:

{ lib, ... }: {
  programs.emacs.decknix.welcome.enable = lib.mkForce false;
}

Common Override Patterns

Add Packages

# ~/.config/decknix/local/home.nix
{ pkgs, ... }: {
  home.packages = with pkgs; [
    kubectl
    helm
    terraform
    awscli2
  ];
}

Shell Aliases

{ ... }: {
  programs.zsh.shellAliases = {
    k = "kubectl";
    tf = "terraform";
  };
}

Environment Variables

{ ... }: {
  home.sessionVariables = {
    EDITOR = "emacsclient -c";
    AWS_PROFILE = "default";
  };
}

macOS System Preferences

# ~/.config/decknix/local/system.nix
{ ... }: {
  system.defaults = {
    dock.autohide = true;
    finder.ShowPathbar = true;
    NSGlobalDomain.KeyRepeat = 2;
  };
}

Homebrew Casks

# ~/.config/decknix/local/system.nix
{ ... }: {
  homebrew.casks = [
    "docker"
    "slack"
    "1password"
  ];
}

Debugging

See which files are loaded:

decknix switch 2>&1 | grep "\[Loader\]"

Evaluate a specific option without building:

nix repl
:lf .
darwinConfigurations.default.config.home-manager.users.YOU.home.packages

Organisation Configs

Org configs let teams share a standard set of tools, packages, and settings through versioned flake inputs.

How It Works

An org config is a separate git repo that exports darwinModules.default and homeModules.default. These are wired into your flake as inputs:

# ~/.config/decknix/flake.nix
{
  inputs = {
    decknix.url = "github:ldeck/decknix";
    nixpkgs.follows = "decknix/nixpkgs";
    nix-darwin.follows = "decknix/nix-darwin";

    # Team config
    my-org-config = {
      url = "github:MyOrg/decknix-config";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ decknix, ... }:
    decknix.lib.mkSystem {
      inherit inputs;
      settings = import ./settings.nix;
      darwinModules = [ inputs.my-org-config.darwinModules.default ];
      homeModules   = [ inputs.my-org-config.homeModules.default ];
    };
}

Creating an Org Config Repo

Minimal structure:

my-org-config/
├── flake.nix
├── home.nix          # Team home-manager modules
├── system.nix        # Team darwin modules
└── README.md

flake.nix

{
  description = "My Org - Decknix Config";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self, nixpkgs, ... }: {
    darwinModules.default = import ./system.nix;
    homeModules.default = import ./home.nix;
  };
}

home.nix

{ pkgs, ... }: {
  home.packages = with pkgs; [
    awscli2
    terraform
    jdk17
  ];
}

Per-User Identity

Each user creates an identity.nix in their org's config directory. The framework's config loader auto-discovers it and generates config.<org>.user.* options available in all Nix modules:

# ~/.config/decknix/my-org/identity.nix
{
  email = "you@my-org.com";
  name = "Your Name";
  githubUser = "your-github";
  gpgKey = "ABCDEF1234567890";   # optional
}

Org modules can then reference the identity without any imports:

# In org config system.nix:
{ config, lib, ... }: {
  decknix.services.hub.jira.email = lib.mkDefault config.my-org.user.email;

  programs.git.includes = [{
    condition = "gitdir:~/Code/my-org/";
    contents.user.email = config.my-org.user.email;
  }];
}

Org bootstraps should prompt for this identity on first setup and write identity.nix automatically. See Config Loader — Identity Files for full details.

Benefits

  • Version pinningflake.lock pins a known-good version
  • Reproducibility — every team member gets the same tools
  • Easy updatesnix flake update my-org-config to pull latest
  • Automated updates — Renovate or Dependabot can watch for new versions
  • Personal overrides — users can still override anything in ~/.config/decknix/<org-name>/
  • Identity wiring — per-user org identity flows automatically via identity.nix

Testing Changes

Before merging changes to an org config, test locally:

cd ~/.config/decknix
decknix switch --override my-org-config=~/Code/my-org/decknix-config
# Or:
nix build .#darwinConfigurations.default.system --impure \
  --override-input my-org-config path:~/Code/my-org/decknix-config

See also: Adding Org Configs for Your Team for a step-by-step walkthrough.

Secrets & Authentication

Decknix keeps secrets separate from your main configuration via gitignored secrets.nix files.

How secrets.nix Works

The config loader discovers and loads secrets.nix files alongside home.nix:

  1. ~/.config/decknix/secrets.nix (root level)
  2. ~/.config/decknix/<org>/secrets.nix (per-org)

Both are merged into your home-manager configuration.

Quick Setup

# ~/.config/decknix/local/secrets.nix
{ ... }: {
  home.file.".authinfo".text = ''
    machine api.github.com login YOUR_USERNAME^forge password ghp_YOUR_TOKEN
  '';
}

Make sure secrets.nix is gitignored:

echo "secrets.nix" >> ~/.config/decknix/.gitignore

GitHub Token for Forge

Forge (GitHub PRs in Emacs) needs a Personal Access Token.

1. Create a Token

  1. Go to GitHub → Settings → Developer Settings → Personal Access Tokens
  2. Generate a classic token with scopes: repo, read:org, read:user
  3. Copy the token (starts with ghp_)

2. Add to secrets.nix

{ ... }: {
  home.file.".authinfo".text = ''
    machine api.github.com login YOUR_USERNAME^forge password ghp_xxxxxxxxxxxx
  '';
}

3. Verify in Emacs

M-x auth-source-search RET
host: api.github.com
user: YOUR_USERNAME^forge

GPG-Encrypted Alternative

# Create and encrypt
echo "machine api.github.com login USER^forge password ghp_xxx" | \
  gpg --encrypt --recipient YOUR_KEY_ID > ~/.authinfo.gpg
{ ... }: {
  programs.emacs.extraConfig = ''
    (setq auth-sources '("~/.authinfo.gpg"))
  '';
}

macOS Keychain

{ ... }: {
  programs.emacs.extraConfig = ''
    (setq auth-sources '(macos-keychain-internet macos-keychain-generic))
  '';
}
security add-internet-password -a "USER^forge" -s "api.github.com" -w "ghp_xxx"

Multi-Account GitHub Setup

For multiple GitHub accounts (personal + work), add entries for each:

{ ... }: {
  home.file.".authinfo".text = ''
    machine api.github.com login personal-user^forge password ghp_personal_xxx
    machine api.github.com login work-user^forge password ghp_work_yyy
  '';
}

When you first use Forge in a repo, it prompts for which username to use. The choice is stored in .git/config.

Combine with Git conditional includes for automatic email switching:

# ~/.config/decknix/my-org/home.nix
{ ... }: {
  programs.git.includes = [{
    condition = "gitdir:~/Code/my-org/";
    contents.user.email = "you@my-org.com";
  }];
}

SSH Keys

{ ... }: {
  programs.ssh = {
    enable = true;
    matchBlocks."github.com" = {
      identityFile = "~/.ssh/id_ed25519";
      user = "git";
    };
    extraConfig = ''
      AddKeysToAgent yes
      UseKeychain yes
    '';
  };
}

GPG Setup

{ pkgs, ... }: {
  home.packages = [ pkgs.gnupg pkgs.pinentry_mac ];
  programs.gpg.enable = true;
  home.file.".gnupg/gpg-agent.conf".text = ''
    pinentry-program ${pkgs.pinentry_mac}/bin/pinentry-mac
    default-cache-ttl 3600
    max-cache-ttl 86400
  '';
}

Nix GitHub Auth

Decknix automatically provides authenticated GitHub API access to Nix (5,000 req/hr instead of 60). This uses gh auth token to generate ~/.config/nix/access-tokens.conf on every decknix switch.

No configuration needed — enabled by default via decknix.nix.githubAuth.enable.

Security Best Practices

  1. Never commit secrets — always gitignore secrets.nix
  2. Use GPG encryption — encrypt .authinfo as .authinfo.gpg
  3. Use short-lived tokens — set token expiration when possible
  4. Limit token scopes — only grant necessary permissions
  5. Prefer SSH — use SSH over HTTPS for git operations
  6. Rotate regularly — update tokens periodically

Enabling & Disabling Features

Every decknix default uses lib.mkDefault, making it easy to override in your personal config.

Emacs Modules

# ~/.config/decknix/local/home.nix
{ ... }: {
  # Disable specific modules
  programs.emacs.decknix.welcome.enable = false;
  programs.emacs.decknix.org.presentation.enable = false;
  programs.emacs.decknix.http.enable = false;

  # Disable specific language modes
  programs.emacs.decknix.languages.rust.enable = false;
  programs.emacs.decknix.languages.go.enable = false;

  # Disable all Emacs config (use your own)
  programs.emacs.decknix.enable = false;
}

See Emacs module reference for all available options.

Editor Profiles

Switch between pre-defined tiers instead of toggling individual modules:

{ ... }: {
  decknix.editors.emacs.profile = "standard";  # minimal | standard | full | custom
  decknix.editors.vim.profile = "minimal";     # minimal | standard | custom
}

Window Manager

{ ... }: {
  # AeroSpace
  decknix.wm.aerospace.enable = false;

  # Or enable with custom workspaces
  decknix.wm.aerospace = {
    enable = true;
    workspaces = {
      "1" = { name = "Terminal"; };
      "2" = { name = "Browser"; };
      "3" = { name = "Code"; };
    };
  };
}

AI Tooling

{ ... }: {
  decknix.cli.auggie.enable = false;
}

Git Features

{ ... }: {
  # Disable Forge (GitHub PRs in Emacs)
  programs.emacs.decknix.magit.forge.enable = false;

  # Disable code-review
  programs.emacs.decknix.magit.codeReview.enable = false;
}

System-Level Features

# ~/.config/decknix/local/system.nix
{ ... }: {
  # Disable Emacs daemon
  services.emacs.decknix.enable = false;

  # Disable AeroSpace system optimisations
  decknix.services.aerospace.enable = false;
}

Using lib.mkForce

When a simple override doesn't work (because another module sets a value explicitly), use mkForce:

{ lib, ... }: {
  programs.emacs.decknix.enable = lib.mkForce false;
}

Use sparingly — in most cases, a normal override is sufficient because the framework uses lib.mkDefault.

Modules Overview

Decknix is organised into modular components, each responsible for a specific area of your environment. Every module uses lib.mkDefault so you can override any setting.

Module Categories

Home-Manager Modules

ModuleDescriptionPage
EmacsFull IDE with 13+ sub-modules, profiles, daemonEmacs →
VimWhitespace cleanup, skim fuzzy finderVim →
Shell & TerminalZsh, Starship prompt, completionsShell →
GitDelta diffs, global config, LFSGit →
Window ManagementAeroSpace, Hammerspoon, SpacesWM →
AI ToolingAugment Code agent, MCP servers, Agent ShellAI →

Darwin (System) Modules

ModuleDescription
System DefaultsPackages (vim, git, curl, skim), Nerd Fonts, Dock/Finder prefs
AeroSpace SystemDisables Stage Manager, Mission Control shortcuts, separate Spaces
Emacs DaemonBackground Emacs service via launchd, ec wrapper command
CLI ModuleInstalls decknix binary, generates extensions config

Core Options

OptionDescriptionDefault
decknix.roleBootstrap template: "developer", "designer", "minimal""developer"
decknix.usernameYour macOS username (set automatically by mkSystem)
decknix.hostnameMachine hostname

Editor Profiles

Instead of toggling individual modules, choose a profile tier:

Emacs Profiles

ProfileModules Included
minimalcore, completion, editing, UI, undo, project
standardminimal + development, magit, treemacs, languages, welcome
full (default)standard + LSP, org-mode, HTTP client, agent-shell
customDisables framework Emacs — bring your own config

Vim Profiles

ProfileModules Included
minimalBase config (exrc, line numbers, secure)
standard (default)minimal + whitespace + skim
customDisables framework Vim — bring your own config
# Change profiles
{ ... }: {
  decknix.editors.emacs.profile = "standard";
  decknix.editors.vim.profile = "minimal";
}

Default Packages

Installed for all users regardless of role:

coreutils · curl · wget · tree · jq · ripgrep · gh

System-level: vim · git · curl · skim

Fonts: JetBrains Mono Nerd Font

Editors

Decknix provides opinionated configurations for two editors: Emacs (full IDE experience) and Vim (lightweight enhancements).

Emacs

A batteries-included Emacs configuration with 13+ modules covering completion, git, LSP, languages, org-mode, and more. Runs as a background daemon on macOS.

Highlights:

  • Modern completion stack (Vertico, Consult, Corfu)
  • Git integration via Magit + Forge (GitHub PRs)
  • LSP support for Kotlin, Java, and more via Eglot
  • 30+ language modes with syntax highlighting
  • Org-mode presentations
  • REST API client
  • AI Agent Shell — multi-session AI interface

Full Emacs documentation

Vim

Lightweight enhancements on top of the base Vim config.

Highlights:

  • Trailing whitespace cleanup (vim-better-whitespace)
  • Fuzzy file finder (skim)
  • Base config: line numbers, exrc, secure mode

Full Vim documentation

Profiles

Both editors support tiered profiles to control how much framework config is applied:

{ ... }: {
  decknix.editors.emacs.profile = "standard";  # minimal | standard | full | custom
  decknix.editors.vim.profile = "minimal";     # minimal | standard | custom
}

Setting custom disables the framework's editor config entirely, letting you bring your own.

Emacs

Decknix provides a modern, batteries-included Emacs experience with 13+ modules, background daemon, and three profile tiers.

Modules

ModuleDescriptionProfile
CoreModus theme, line numbers, better defaultsminimal+
CompletionVertico, Consult, Corfu, Embarkminimal+
EditingSmartparens, Crux, Move-text, EditorConfigminimal+
UIWhich-key, Helpful, Nerd-iconsminimal+
Undoundo-fu, vundo (visual undo tree)minimal+
ProjectProject management and navigationminimal+
WelcomeStartup screen with keybinding cheat sheetstandard+
DevelopmentFlycheck, Yasnippetstandard+
MagitGit interface, Forge (GitHub PRs), code-reviewstandard+
TreemacsProject file tree with git integrationstandard+
Languages30+ language modes with syntax highlightingstandard+
LSPEglot, kotlin-ls, jdt-ls, dape (debugging)full
Org-modeModern styling, presentations (Olivetti)full
HTTPREST client, jq integration, org-babelfull
Agent ShellAI agent interface (Augment Code)full

Key Bindings — Quick Reference

KeyAction
C-sSearch in buffer (consult-line)
C-x bSwitch buffer with preview
M-s rProject-wide ripgrep search
M-yBrowse kill ring
C-.Context actions (Embark)

Git (Magit)

KeyAction
C-x gMagit status
@ f fFetch forge topics (PRs/issues)
@ c pCreate pull request
@ l pList pull requests

File Tree (Treemacs)

KeyAction
C-x t tToggle treemacs
C-x t fFind current file in tree

LSP / Code

KeyAction
C-c l rRename symbol
C-c l aCode actions
C-c l fFormat region
C-c l FFormat buffer
C-c l dShow documentation

Debugging (dape)

KeyAction
C-c d dStart debugger
C-c d bToggle breakpoint
C-c d nStep over
C-c d sStep in
C-c d cContinue

Editing

KeyAction
C-aSmart home (Crux)
C-c dDuplicate line
M-up/downMove line/region
C-/Undo
C-?Redo
C-x uVisual undo tree (vundo)

Org-mode

KeyAction
F5 or C-c pStart/stop presentation
n / pNext/previous slide

Languages

30+ languages with syntax highlighting:

CategoryLanguages
PrimaryKotlin, Java, Scala, SQL, Terraform/HCL, Shell, Nix, Python
DataJSON, YAML, TOML, XML, Markdown
WebHTML, CSS/SCSS/LESS, JavaScript, TypeScript, JSX, Vue, Svelte

Emacs Daemon

Configured in modules/darwin/emacs.nix, enabled by default on macOS. Runs as a background launchd service — no Dock icon, no Cmd+Tab entry.

ec filename          # Open file in Emacs
ec -c -n             # New GUI frame
ec -c -n file.txt    # Open file in new GUI frame
ec -t file.txt       # Open in terminal
emacsclient -c       # Create new GUI frame

GUI frames appear in the Dock while open; closing a frame doesn't kill the daemon.

OptionDefaultDescription
services.emacs.decknix.enabletrueEnable Emacs daemon
services.emacs.decknix.packagepkgs.emacsEmacs package to use
services.emacs.decknix.additionalPath[]Extra PATH entries for daemon

Module Options Reference

Disabling Modules

{ ... }: {
  programs.emacs.decknix.enable = false;             # ALL emacs config
  programs.emacs.decknix.welcome.enable = false;     # Welcome screen
  programs.emacs.decknix.magit.enable = false;       # Git interface
  programs.emacs.decknix.magit.forge.enable = false;  # Just Forge
  programs.emacs.decknix.completion.enable = false;  # Completion stack
  programs.emacs.decknix.treemacs.enable = false;    # File tree
  programs.emacs.decknix.undo.enable = false;        # Undo enhancements
  programs.emacs.decknix.editing.enable = false;     # Editing enhancements
  programs.emacs.decknix.development.enable = false; # Flycheck/Yasnippet
  programs.emacs.decknix.ui.enable = false;          # UI enhancements
  programs.emacs.decknix.ui.icons.enable = false;    # Just icons
  programs.emacs.decknix.org.enable = false;         # Org enhancements
  programs.emacs.decknix.lsp.enable = false;         # LSP/IDE
  programs.emacs.decknix.http.enable = false;        # REST client
  programs.emacs.decknix.languages.enable = false;   # All languages
}

Customisation

{ pkgs, ... }: {
  # Add your own packages
  programs.emacs.extraPackages = epkgs: [ epkgs.evil epkgs.lsp-mode ];

  # Add your own config
  programs.emacs.extraConfig = ''
    (evil-mode 1)
    (setq my-custom-variable t)
  '';
}

Note: Evil mode (Vim emulation) is not included by default. Add it in your personal config as shown above.

Vim

Decknix provides lightweight Vim enhancements on top of a sensible base config.

Base Config (All Profiles)

  • set exrc — load project-local .vimrc
  • set secure — restrict commands in project .vimrc
  • Line numbers enabled

Whitespace Module

Plugin: vim-better-whitespace

Automatically strips trailing whitespace on save.

OptionDefaultDescription
programs.vim.decknix.whitespace.enabletrue (standard profile)Enable whitespace cleanup
programs.vim.decknix.whitespace.stripModifiedOnlytrueOnly strip modified lines
programs.vim.decknix.whitespace.confirmfalsePrompt before stripping

Skim Module

Plugin: skim (fuzzy finder)

Integrates skim into Vim for fast file and buffer searching.

OptionDefaultDescription
programs.vim.decknix.skim.enabletrue (standard profile)Enable skim integration

Profiles

ProfileIncludes
minimalBase config only
standard (default)Base + whitespace + skim
customDisables framework Vim entirely
{ ... }: {
  decknix.editors.vim.profile = "minimal";
}

Adding Your Own Config

{ ... }: {
  programs.vim = {
    enable = true;
    plugins = [ pkgs.vimPlugins.vim-surround ];
    extraConfig = ''
      set relativenumber
    '';
  };
}

Shell & Terminal

Decknix configures Zsh as the default shell with modern enhancements.

Zsh

Enabled by default with:

  • Completion — case-insensitive, menu-driven completion
  • Autosuggestion — fish-like suggestions from history
  • Syntax highlighting — command validation as you type
  • History — 50,000 entries, shared across sessions, prefix-based search

Starship Prompt

Starship provides a fast, customisable prompt showing git status, language versions, and more.

Customise the prompt character:

{ ... }: {
  programs.starship.settings.character = {
    success_symbol = "[➜](bold green)";
    error_symbol = "[✗](bold red)";
  };
}

Inline Timestamp

The prompt line shows a wall-clock timestamp grouped with the command-duration so they read as a pair:

~/.config/decknix on ☁️  lachlan@example.com · 2026-04-30T14:32:15 · took 45s
➜

Configure via programs.starship.decknix.timestamp.*:

{ ... }: {
  programs.starship.decknix.timestamp = {
    enable    = true;                   # default
    format    = "%Y-%m-%dT%H:%M:%S";    # default (ISO 8601)
    separator = " · ";                  # default
    style     = "dimmed";               # default
  };
}

format accepts any Chrono strftime string. Common alternatives:

FormatRenders as
%Y-%m-%dT%H:%M:%S2026-04-30T14:32:15
%T14:32:15
%H:%M14:32
%a %H:%MTue 14:32
%I:%M %p02:32 PM

Set enable = false to drop the timestamp and revert cmd_duration to upstream defaults.

Delta (Diff Viewer)

Delta is configured as the default git pager, providing syntax-highlighted diffs.

direnv

direnv is enabled by default, so cd-ing into a directory with an authorised .envrc automatically loads its environment (and unloads it on the way out). The zsh hook is wired automatically — no manual eval "$(direnv hook zsh)" needed.

nix-direnv is included for fast, cached use nix / use flake support, keeping the dev-shell alive as a GC root so it reloads instantly instead of re-evaluating on every cd. A minimal flake-based .envrc:

# .envrc — run `direnv allow` once to authorise it
use flake

Disable it (or drop nix-direnv) via your personal config:

{ ... }: {
  programs.direnv.enable = false;            # disable entirely
  # or keep direnv but skip nix-direnv:
  # programs.direnv.nix-direnv.enable = false;
}

Default Shell Aliases

Decknix doesn't impose shell aliases — add your own:

{ ... }: {
  programs.zsh.shellAliases = {
    ll = "ls -la";
    gs = "git status";
    gp = "git pull --rebase";
  };
}

Extra Init

Add custom shell initialization:

{ ... }: {
  programs.zsh.initExtra = ''
    # Source work credentials
    [[ -f ~/.config/secrets/env.sh ]] && source ~/.config/secrets/env.sh
  '';
}

Session Variables

{ ... }: {
  home.sessionVariables = {
    EDITOR = "emacsclient -c";
    VISUAL = "emacsclient -c";
  };
}

Git

Decknix provides a sensible Git configuration with modern tooling.

Default Settings

SettingValueDescription
init.defaultBranchmainDefault branch name
pull.rebasetrueRebase on pull instead of merge
push.autoSetupRemotetrueAuto-create remote tracking branch
core.pagerdeltaSyntax-highlighted diffs
lfs.enabletrueGit Large File Storage

Delta Integration

Delta provides beautiful, syntax-highlighted diffs in the terminal with line numbers.

Customising Git

# ~/.config/decknix/local/home.nix
{ ... }: {
  programs.git.settings = {
    user.email = "you@example.com";
    user.name = "Your Name";
    core.editor = "emacsclient -c";
  };
}

Conditional Includes

Use different identities for different directories:

{ ... }: {
  programs.git.includes = [{
    condition = "gitdir:~/Code/work/";
    contents = {
      user.email = "you@company.com";
      user.name = "Your Name";
      commit.gpgsign = true;
    };
  }];
}

Magit (Emacs Git Interface)

The Emacs module includes Magit — a full Git interface inside Emacs:

  • C-x g → Git status
  • Stage, commit, push, pull, rebase — all from keyboard
  • Forge — manage GitHub PRs and issues without leaving Emacs
  • code-review — inline PR review with comments

See Secrets & Authentication for Forge token setup.

GitHub CLI

The gh CLI is installed by default. Decknix also auto-configures authenticated GitHub API access for Nix itself via decknix.nix.githubAuth (see Secrets).

Window Management

Decknix supports several window management approaches for macOS, from full tiling to simple snapping. Pick the style that suits your workflow — they can be used individually or combined.

ToolStyleSourceDecknix module
AmethystAutomatic tiling (xmonad-style)nix-casks— (package only)
AeroSpaceManual tiling (i3-style)nixpkgsdecknix.wm.aerospace
RectangleWindow snapping (keyboard shortcuts)nix-casks— (package only)
SpaceIdMenu-bar space indicatornix-casks— (package only)
Native macOSStage Manager, Split View, Spacesbuilt-in

Amethyst

Amethyst is an automatic tiling window manager for macOS in the style of xmonad. Windows are arranged automatically into layouts (tall, wide, fullscreen, column, BSP, etc.) and reflow when windows are added or removed.

Amethyst is the recommended starting point — it works with native macOS Spaces, requires no SIP disabling, and is fully configurable via its preferences pane or defaults write.

Installation

Amethyst is not in nixpkgs; install it via nix-casks:

# home.nix (personal overrides)
{ pkgs, inputs, ... }: {
  home.packages =
    with inputs.nix-casks.packages.${pkgs.stdenv.hostPlatform.system}; [
      amethyst
    ];
}

Configuration

Amethyst stores its configuration in macOS user defaults (com.amethyst.Amethyst). Key settings:

Default keyDescription
layoutsActive layout cycle (e.g., tall, wide, fullscreen, column)
mod1Primary modifier (default: option + shift)
mod2Secondary modifier (default: ctrl + option + shift)
enables-layout-hudShow layout name on switch
window-marginsEnable gaps between windows
floatingList of app bundle IDs to float

Note — Glide: Glide is a newer tiling WM by the same author as Amethyst. It is not yet recommended for regular use and is not included in the default configuration. If you want to experiment with it, install via nix-casks and use it in place of Amethyst.

AeroSpace

AeroSpace is a manual tiling window manager inspired by i3. Unlike Amethyst's automatic layouts, AeroSpace gives you direct control over window placement using keyboard commands. It manages its own virtual workspaces (separate from macOS Spaces).

Decknix provides both home-manager and darwin modules for AeroSpace.

Enabling

# home.nix
{ ... }: {
  decknix.wm.aerospace.enable = true;
}

Options

OptionDefaultDescription
decknix.wm.aerospace.enablefalseEnable AeroSpace
decknix.wm.aerospace.prefixKey"cmd+alt"Prefix key for commands
decknix.wm.aerospace.keyStyle"emacs""emacs" (arrows) or "vim" (hjkl)
decknix.wm.aerospace.showModeHintsfalseShow mode notifications
decknix.wm.aerospace.fuzzyPicker.enabletrueSpotlight-like workspace/window picker

Workspaces

Define named workspaces with optional monitor assignment:

{ ... }: {
  decknix.wm.aerospace.workspaces = {
    "1" = { name = "Terminal"; };
    "2" = { name = "Browser"; };
    "3" = { name = "Code"; };
    "4" = { name = "Chat"; monitor = "secondary"; };
  };
}

Default workspaces: 1–5 (main, web, term, mail, chat) + D, E, N, M, S (decknix, emacs, notes, music, system).

System-Level Settings

The darwin module optimises macOS for tiling:

OptionDefaultEffect
decknix.services.aerospace.disableStageManagertruePrevents conflicts
decknix.services.aerospace.disableSeparateSpacestrueMulti-monitor support
decknix.services.aerospace.disableMissionControlShortcutstrueFrees Ctrl+arrow keys
decknix.services.aerospace.autohideDocktrueMaximises screen space

Rectangle

Rectangle is a lightweight window snapping tool. It provides keyboard shortcuts for half-screen, quarter-screen, thirds, and other arrangements — similar to Windows snap or Spectacle (which Rectangle succeeds).

Rectangle is a good choice if you prefer manual control without full tiling. It pairs well with Amethyst (use Amethyst for your main workspace and Rectangle shortcuts for quick one-off snaps).

Installation

# home.nix
{ pkgs, inputs, ... }: {
  home.packages =
    with inputs.nix-casks.packages.${pkgs.stdenv.hostPlatform.system}; [
      rectangle
    ];
}

Rectangle is configured via its preferences pane. No Decknix module is needed.

SpaceId

SpaceId shows the current macOS Space number in the menu bar. Useful alongside any window manager to keep track of which Space you're on.

Installation

# home.nix
{ pkgs, inputs, ... }: {
  home.packages =
    with inputs.nix-casks.packages.${pkgs.stdenv.hostPlatform.system}; [
      spaceid
    ];
}

Hammerspoon

Hammerspoon provides Lua-based macOS automation. Decknix uses it for space navigation bindings.

{ ... }: {
  decknix.wm.hammerspoon = {
    enable = true;
    modifier = "Meta + Ctrl";
  };
}

Generates Lua configuration with space navigation bindings.

Spaces

WM-agnostic multi-monitor workspace management with named space groups and picker scripts:

{ ... }: {
  decknix.wm.spaces.workspaces = {
    dev = {
      name = "Development";
      startSpace = 1;
      spaces = [ "terminal" "editor" "browser" ];
      key = "d";
    };
  };
}

Generates space picker scripts with shortcodes for quick switching. Works with any WM or standalone.

AI Tooling

Decknix provides a declarative, Nix-managed AI development environment — from CLI agent configuration to a full Emacs-native agent interface with session management, work context awareness, and prompt engineering tools.

What's Included

ComponentDescriptionPage
Auggie CLIAugment Code agent with Nix-managed settings and MCP serversConfiguration →
Agent ShellEmacs-native multi-session AI interface (7 sub-modules)Agent Shell →

Design Principles

  1. Declarative first — all configuration lives in Nix. decknix switch reproduces your entire AI setup on any machine.
  2. Runtime-mutable — settings are copied (not symlinked) so tools can modify them at runtime. The next decknix switch resets to the Nix-managed baseline.
  3. Composable — each component is independently toggleable. Use the CLI without Emacs, or Emacs without MCP servers.
  4. Session-as-first-class — AI conversations are persistent objects with tags, context items, and metadata — not disposable chat buffers.

Architecture

┌─────────────────────────────────────────────────┐
│  Nix Configuration (agent-shell.nix, auggie.nix)│
│  ┌──────────┐  ┌──────────┐  ┌────────────────┐│
│  │ Settings  │  │ MCP      │  │ Commands &     ││
│  │ & Model   │  │ Servers  │  │ Templates      ││
│  └─────┬─────┘  └─────┬────┘  └───────┬────────┘│
└────────┼──────────────┼───────────────┼──────────┘
         ▼              ▼               ▼
  ~/.augment/     ~/.augment/    ~/.claude/commands/
  settings.json   settings.json  ~/.emacs.d/snippets/
         │              │               │
         ▼              ▼               ▼
┌─────────────────────────────────────────────────┐
│  Emacs Agent Shell                              │
│  ┌──────┐ ┌─────────┐ ┌────────┐ ┌───────────┐│
│  │Core  │ │Sessions │ │Prompts │ │Context    ││
│  │ACP   │ │Tags     │ │Commands│ │CI/PR/Issue││
│  └──────┘ └─────────┘ └────────┘ └───────────┘│
└─────────────────────────────────────────────────┘

Quick Start

AI tooling is enabled by default in the full Emacs profile. To start:

C-c A a    → Start an agent session
C-c A s    → Session picker (live + saved + new)
C-c A ?    → Full keybinding reference

For CLI-only usage without Emacs:

auggie                    # Interactive agent session
auggie session list       # List saved sessions
auggie session resume ID  # Resume a session

Next Steps

AI Configuration

All AI tooling is configured declaratively in Nix and deployed via decknix switch.

Auggie CLI

Enabling

{ ... }: {
  decknix.cli.auggie.enable = true;
}

Settings

{ ... }: {
  decknix.cli.auggie.settings = {
    model = "opus4.6";
    indexingAllowDirs = [
      "~/tools/decknix"
      "~/Code"
    ];
  };
}

Settings are written to ~/.augment/settings.json. The file is copied (not symlinked) so auggie can modify it at runtime; the next decknix switch overwrites with the Nix-managed version.

MCP Servers

Declaratively configure Model Context Protocol servers:

{ ... }: {
  decknix.cli.auggie.mcpServers = {
    context7 = {
      type = "stdio";
      command = "npx";
      args = [ "-y" "@upstash/context7-mcp@latest" ];
      env = {};
    };
    "gcp-monitoring" = {
      type = "stdio";
      command = "npx";
      args = [ "-y" "gcp-monitoring-mcp" ];
      env.GOOGLE_APPLICATION_CREDENTIALS = "~/.config/gcloud/credentials.json";
    };
  };
}

MCP servers are written into the mcpServers section of ~/.augment/settings.json.

Slack MCP Workspaces

Connect auggie to one or more Slack workspaces using the official Slack MCP server:

{ ... }: {
  decknix.cli.auggie.slack.workspaces = {
    acme-corp = {
      clientId = "3660753192626.123456";
      description = "ACME Corp team workspace";
    };
    personal = {
      clientId = "3660753192626.789012";
    };
  };
}

Each workspace generates a slack-<name> entry in mcpServers pointing at https://mcp.slack.com/mcp with the workspace's CLIENT_ID for OAuth authentication.

Setup requirements:

  1. Create or reuse a Slack app at api.slack.com/apps
  2. Enable OAuth with appropriate scopes (e.g., search:read.public, chat:write, channels:history)
  3. Publish as an internal app or to the Slack Marketplace
  4. Copy the Client ID from the app's OAuth settings

Multiple workspaces merge naturally — define some in your org config, others in your personal config, and they all appear in settings.json.

Viewing Configured Servers

From Emacs: C-c A S opens a formatted buffer showing all configured MCP servers with their type, command, args, and environment variables.

Runtime vs Nix-Managed

SourcePersists across decknix switch?How to add
Nix config✅ Yesdecknix.cli.auggie.mcpServers
auggie mcp add❌ No (temporary)Runtime command

Agent Shell Module

The Emacs agent-shell module is enabled by default in the full profile:

{ ... }: {
  programs.emacs.decknix.agentShell = {
    enable = true;           # Core agent-shell.el + ACP
    manager.enable = true;   # Tabulated session dashboard
    workspace.enable = true; # Dedicated tab-bar workspace
    attention.enable = true; # Mode-line attention tracker
    templates.enable = true; # Yasnippet prompt templates
    commands.enable = true;  # Nix-managed slash commands
    context.enable = true;   # Work context panel (issues, PRs, CI)
  };
}

Each sub-module can be independently disabled. See Agent Shell Overview for details on each component.

Per-Purpose Provider & Model

Automated agent launches (PR reviews, bot-authored PR reviews) and the interactive new-session path can pin a specific (provider, model, mode) triple via Nix, independent of the interactive decknix-agent-default-provider. Three purposes ship today:

PurposeTriggerDefault providerDefault modelDefault mode
pr-reviewC-c A c r, sidebar Requests row, batch processorclaude-codesonnetauto
bot-pr-reviewAuto-review dispatch on bot-authored PRs, or matched by author heuristicclaude-codesonnetauto
new-sessionInteractive / QUICK C-c A n (its provider also feeds decknix-agent-default-provider)claude-codenullauto
{ ... }: {
  programs.emacs.decknix.agentShell.purposes = {
    # Human PR reviews go through Claude with opus for depth.
    pr-review     = { provider = "claude-code"; model = "opus"; mode = "auto"; };
    # Bot diffs are shallow — pin the cheapest capable model.
    bot-pr-review = { provider = "claude-code"; model = "haiku"; mode = "auto"; };
    # Start new interactive sessions on Claude in auto (no per-command prompts).
    new-session   = { provider = "claude-code"; mode = "auto"; };
  };
}

Validation. All three fields are validated at daemon start:

  • provider must be a registered provider id (built-ins: auggie, claude-code, pi). Unknown values coerce to decknix-agent-default-provider with a warning to *Warnings*.
  • model must appear in decknix-agent-known-models for the chosen provider (or be null to defer to the provider default). Unknown values drop to nil with a warning.
  • mode is a session/permission mode honoured only by providers that expose one — today claude-code, whose ids are default, auto, acceptEdits, bypassPermissions, and plan. For providers without session modes (Auggie, Pi) it drops to nil at boot with a warning. Set to null to keep the provider's own default.

Resume semantics. For launch-flag providers (Auggie) the model is appended as --model <id>; for flagless providers (Claude, Pi) it is replayed over ACP once the session reports ready. The permission mode is baked into the session config and applied by agent-shell once the session reports ready. Either way, once you switch mid-session with C-c C-v (model) or C-c C-m (mode), that per-conversation choice persists in ~/.config/decknix/agent-sessions.json and wins over the purpose default on both resume and fork.

Scope. The two *-review purposes are consulted by the automated review launchers; new-session seeds interactive C-c A n. C-c A f (fork) inherits the source conversation's persisted model/mode (falling back to the new-session defaults), and the sidebar worktree w s action keeps decknix-agent-default-provider with no model pin. See Model Selection for the full override-lever hierarchy.

Custom Commands

Nix-managed commands are deployed to ~/.claude/commands/ (the shared slash-command location read natively by both Claude Code and Auggie) and also to ~/.pi/agent/prompts/ (where Pi reads them as /name prompt templates), so a single source covers every supported agent. User-created commands (regular files) coexist in each directory and are not affected by decknix switch.

# Commands are defined in agent-shell.nix and deployed automatically.
# To add your own at runtime:
# C-c c n  → Create new command (opens template in ~/.claude/commands/)

See Productivity for the full command framework.

Claude Permissions

Claude Code prompts before running any Bash command unless it matches a rule in ~/.claude/settings.json (permissions.allow). Skills ship executable helper scripts (deployed 755 via decknix.cli.agentSync with executable = true), so without an allow rule Claude asks for permission every session before running them.

By default decknix auto-allowlists every Nix-installed executable tool it manages — each executable agent-sync file becomes a narrow Bash(<abs-path>:*) prefix rule (framework- and org-registered scripts alike):

{ ... }: {
  decknix.ai.claude = {
    enable = true;

    # Auto-allow decknix/Nix-installed executable skill scripts (default: true).
    permissions.allowManagedTools = true;

    # Extra rules merged in alongside the managed-tool rules.
    permissions.allow = [
      "Bash(gh pr view:*)"
    ];

    # DENY rules always win over allow — the backstop for a broad allow-list.
    permissions.deny = [
      "Bash(sudo:*)"
      "Read(~/.config/decknix/**/secrets/**)"
    ];
  };
}

Both lists are deep-merged into ~/.claude/settings.json — decknix updates only .permissions.allow and .permissions.deny (each union + de-duplicated) and leaves every other key untouched, because Claude mutates this file at runtime (e.g. it writes skipDangerousModePermissionPrompt). Set allowManagedTools = false to manage the allowlist entirely by hand.

Allowlist vs. blacklist model

A path-scoped allowlist rots the moment a managed tool moves on disk: every stale Bash(<old-abs-path>:*) rule silently matches nothing, and Claude re-prompts for the relocated tool every session. Two ways to avoid that:

  • Allowlist (default). Keep allowManagedTools = true and let the auto-derived rules track each tool's current absolute path. They regenerate on every decknix switch, so a move updates the rule automatically — provided the tool stays a managed executable agent-sync file.

  • Blacklist. Allow the common tools broadly and lean on permissions.deny as the backstop:

    decknix.ai.claude.permissions = {
      allowManagedTools = false;              # broad Bash below supersedes them
      allow = [ "Bash" "Read" "Edit" "Write" ];
      deny  = [
        "Read(~/.config/decknix/**/secrets/**)"
        "Bash(sudo:*)"
        "Bash(rm -rf /:*)"
        "Bash(rm -rf ~:*)"
      ];
    };
    

    A bare Bash allow can't rot when a script moves, so this trades a maintained allowlist for a small never-do denylist. Two caveats:

    • deny is evaluated before allow and always wins, but the host's own destructive-command classifier still runs independently — it remains the real backstop even under a broad allow.
    • A Read(...) deny guards the Read/Edit tool path only. Under a broad Bash allow a shell cat/< can still reach a denied file, so treat secret denies as defence-in-depth (and against accidental whole-dir slurps), not a hard boundary. Keep secrets 0600 and out of the Nix store regardless.

Leave defaultMode at default for the blacklist model. bypassPermissions would skip permission evaluation entirely — including your own deny rules — which is the opposite of what a denylist is for.

Claude MCP Servers

Configure MCP servers for Claude Code globally — every workspace inherits them without a per-repo .mcp.json:

{ ... }: {
  decknix.ai.claude = {
    enable = true;

    mcpServers = {
      # Atlassian (Jira + Confluence) via the mcp-remote bridge.  First
      # invocation opens a browser flow for OAuth; after that Claude has
      # native Jira/Confluence tools and no longer shells out to
      # `auggie --print --ask` for issue lookups (the previous workaround
      # was ~2 min per call).
      atlassian = {
        type = "stdio";
        command = "npx";
        args = [ "-y" "mcp-remote" "https://mcp.atlassian.com/v1/sse" ];
      };
    };
  };
}

The shape mirrors decknix.cli.auggie.mcpServers — stdio servers use type / command / args / env; remote servers use type = "http" (or "sse") plus url and optional headers.

Entries are deep-merged into ~/.claude.json (.mcpServers) on every decknix switch. Claude mutates this file heavily at runtime (skillUsage, cached* caches, OAuth tokens, migration flags), so decknix only touches .mcpServers and leaves the ~40 other runtime-managed keys alone:

  • Nix-declared entries win against runtime-added entries with the same name.
  • Runtime-added entries with unrelated names are preserved.
  • Removing an entry from Nix does not remove it from ~/.claude.json (Claude may have converged on it independently); purge those with /mcp remove <name> inside Claude.

Reach for a workspace-local .mcp.json only when a server should be strictly project-local (e.g. a repo-scoped test harness). Global config here keeps personal + org tooling consistent across every project you open.

Agent Shell

The Emacs Agent Shell is a native, multi-session AI agent interface built on agent-shell.el and the Augment Code Protocol (ACP). It turns Emacs into a first-class AI development environment where sessions are persistent, context-aware, and deeply integrated with your workflow.

Why Not Just a Chat Buffer?

Most AI integrations treat conversations as disposable text. Agent Shell treats them as first-class objects:

  • Sessions persist — resume any conversation from days ago with full context
  • Sessions have metadata — tags, pinned issues, CI status, review threads
  • Sessions are searchable — find any past session by keyword or tag
  • Sessions are composable — structured prompts via templates and slash commands
  • Sessions are work-aware — auto-detect issues, PRs, and Jira tickets from conversation text

Package Ecosystem

Agent Shell is assembled from 6 packages using tiered sourcing:

PackageSourcePurpose
shell-makernixpkgs unstableComint-like shell buffer management
acpnixpkgs unstableAugment Code Protocol client
agent-shellnixpkgs unstableCore agent interface
agent-shell-managerCustom derivationTabulated session dashboard
agent-shell-workspaceCustom derivationDedicated tab-bar workspace
agent-shell-attentionCustom derivationMode-line attention tracker

Plus ~1,800 lines of custom Elisp in agent-shell.nix providing sessions, tags, compose, commands, templates, and context awareness.

Layers

The implementation is organised into 5 layers, each building on the previous:

LayerNameWhat It ProvidesPage
1FoundationCore shell, ACP protocol, package sourcingFoundation →
2Multi-SessionSession picker, resume, history, quitMulti-Session →
3ProductivityCompose buffer, templates, commands, tagsProductivity →
4IntegrationMCP servers, declarative tool configIntegration →
5ContextIssues, PRs, CI status, review threadsContext →

Quick Reference

C-c A a    Start / switch to agent
C-c A s    Session picker (live + saved + new)
C-c A e    Compose multi-line prompt
C-c A ?    Full keybinding help
C-c A I    Context panel (issues, PRs, CI)

Inside an agent-shell buffer, drop the A prefix: C-c s, C-c e, C-c ?, etc.

Full keybinding reference

Guided Tour

A "screencast" of the Agent Shell, grouped by topic — from the welcome screen to opening the sidebar and running the everyday flows. Each step shows the keys pressed and a mock of what appears. Colours are the real Emacs face colours (same palette as the Sidebar Layouts); the frames are mock-ups, so spacing is approximate — colour and layout are the contract.

Legend — provider glyph A Auggie · C Claude · P Pi. Session state:

{dm}○{/} initializing   {y}◐{/} working   {r}◐{/} waiting (needs input)
{g}●{/} ready          {ac}●{/} finished  {r}●{/} killed

Getting oriented

The welcome screen

Emacs opens on the *decknix* buffer. Press C-c w to return to it any time.

emacs

  {hd}       __     _   __  __  _   ___ ___{/}
  {ac}   __| |___ / | | ' \|  \/  || | / __/ __| {/}
  {c}  / _` / -_) || |  ' <| |\/| || || \__ \__ \ {/}
  {br}  \__,_\___|_||_|_|\_\_|  |_||_|/_|___/___/ {/}

  {bd}Welcome to dEckMACS{/} — a modern, batteries-included Emacs config
  {dm}Vertico • Consult • Marginalia • Corfu • Embark • Magit{/}

  {c bd}1{/} Navigation & Search   {dm}C-s   M-s r   C-x b{/}
  {c bd}2{/} Editing               {dm}C-/   C-?     C-x u{/}
  {c bd}3{/} Completion & Code     {dm}TAB   C-.     M-n/p{/}
  {c bd}4{/} Git (Magit)           {dm}C-x g C-x M-g s / u{/}
  {c bd}5{/} Buffers & Windows     {dm}C-x C-f C-x k C-x 1{/}
  {c bd}6{/} Help & Discovery      {dm}C-h f C-h k  C-h t{/}

  {dm}Press 1-6 for full cheat sheets • r refresh • q quit{/}
  {dm}C-h ? help • C-x b buffers • C-x C-f files • C-c w this screen{/}

The shipping welcome buffer. Numbers 16 open per-topic cheat sheets.

…with agents surfaced (proposed)

The welcome screen is a natural home for a live agent summary and quick-actions into the Agent Shell. A proposed addition — a category 7 plus a status strip that mirrors the sidebar's Live section:

  {c bd}6{/} Help & Discovery      {dm}C-h f C-h k  C-h t{/}
  {c bd}7{/} {ac bd}AI Agents{/}            {dm}C-c A a start  C-c A w sidebar  C-c A s sessions{/}

  {dm}Agents{/}  {y}◐ 2 working{/}   {r}◐ 1 waiting{/}   {g}● 3 ready{/}   {dm}·  C-c A w to open{/}

Proposed, not yet shipped — mocked to show the intent (see How It Compares → roadmap).

Open the Agents sidebar

C-c A w

The four-section workspace sidebar opens on the right: Requests → WIP → Live → Sessions.

{hd}Requests (3){/} ⇅
{c}⎇ {/} {y}2d{/} upside{gd}#16570{/} {g}●{/}{gd bd}@{/}{ac}◉{/} {gd}CSE-201: refactor token cache{/}
{dm bd}↓ {/}{r}15d{/} upside#16568 {bo}π{/}   {dm}bump: dependency update{/}
{dm bd}↓ {/} {y}1d{/} reapit#123 {r}◐{/}{c bd}@{/} CSE-204: fix pubsub timeout

{hd}WIP (2){/}
{g bd}⎇*{/} {g}●{/} decknix{gd}#812{/}       [3⬆ 2✓]
{c}⎇ {/} {y}★{/} decknix-config#77  {y}draft{/}

{hd}Live (2){/}
 > {y}A{/} {y}◐{/} feature/token-cache  {rp}decknix{/}   [2⬆ 1✓] {r bd}📥1{/}
   {g}C{/} {g}●{/} review/auth          {rp}decknix{/}   {sg bd}↩{/}

{hd}Sessions (18){/}  {dm}…recent by tag / date{/}

Colour is load-bearing: green approved/ready, red needs-you/CI-fail, yellow draft/working, blue branch, pink bot. ⎇* = branch live in a session; = a review session is already open on that PR.


Running a session

Start a session

C-c A n

A provider picker appears (Vertico). Choose the agent to launch.

{dm}Start agent (provider):{/}
 {ac}>{/} {g}C{/}  Claude Code        {dm}(default · claude-code){/}
   {y}A{/}  Auggie             {dm}(Augment Code){/}
   {c}P{/}  Pi                 {dm}(Contextual AI){/}
   {dm}?{/}  Gemini             {dm}(Google){/}

Pick C and a fresh Claude session buffer opens; the Live section gains a row.

Watch its sub-agents

C-c A w

As the agent spins up sub-agents, the Live section shows each with its own state — colour tells you at a glance who needs you and who's done.

{hd}Live (1 · 3 sub){/}
 > {g}C{/} {y}◐{/} feature/token-cache   {rp}decknix{/}
     {dm}├─{/} {g}●{/} explore   {dm}mapped cache call-sites{/}
     {dm}├─{/} {y}◐{/} implement {dm}editing cache.rs …{/}
     {dm}└─{/} {r}◐{/} verify    {r bd}needs input{/} {dm}approve test run?{/}

Per-sub-agent status + colour + a fold toggle for completed ones is the near-term build (Feature 1 of the resourcing roadmap); mocked as the target UX.

Compose a multi-line prompt

C-c A e

A dedicated compose buffer opens — write freely, then submit.

{dm}─ *compose: feature/token-cache* ──────────────{/}
 Refactor the token cache to expire entries lazily
 on read instead of a background sweep. Keep the
 public API stable; add a test for the 410 path.

{dm}────────────────────────────────────────────────{/}
 {ac}C-c C-c{/} submit   {dm}C-c C-k cancel   M-p/M-n history   M-r search{/}

C-c C-c sends it; M-p/M-n walk prompt history, M-r searches it (consult).


Reviewing & shipping

Review a PR

C-c A cr

From a Requests row (or the quick-action), launch a review session pre-linked to the PR.

{hd}Requests (3){/}
 {ac}>{/} {c}⎇ {/} {y}2d{/} upside{gd}#16570{/} {g}●{/}{gd bd}@{/} {gd}CSE-201: refactor token cache{/}
       {dm}review session launching…{/} {ac}◉{/}
   {dm bd}↓ {/}{r}15d{/} upside#16568 {bo}π{/} {dm}bump: dependency update{/}

The marks the row now has a live review session. C-c s l links another PR to the current session; C-c s u unlinks.

C-c s i

C-c s i prints a one-line session summary; C-c s t a adds a tag; C-c s l links a PR.

{dm}Session:{/} 3f9a1c2e…  {dm}Conv:{/} b71e…  {dm}Model:{/} {g}opus{/}  {dm}Mode:{/} {y}auto{/}
{dm}Tags:{/} {c}#token-cache #CONN-473{/}   {dm}Exchanges:{/} 14
{dm}Workspace:{/} ~/tools/decknix
{dm}Created:{/} 2026-07-08 09:12   {dm}Modified:{/} 2026-07-08 13:47

Tags, workspace, model, permission-mode and linked PRs all persist per-conversation and survive resume/fork.

Copy a region as Slack / HTML / PDF

C-c x s

Select any Markdown in the buffer and export it — s Slack mrkdwn, h HTML, P PDF, t plain / re-align table.

{dm}region (Markdown):{/}
  **Root cause:** the `/rest/api/2/search` endpoint was removed.
  See [the changelog](https://x/CHANGE-20).

{ac}C-c x s{/}  {dm}→ clipboard (Slack mrkdwn):{/}
  {g}*Root cause:* the `/rest/api/2/search` endpoint was removed.
  {/}

Fence- and table-aware, pure-string transforms — paste straight into Slack, a PR comment, or a doc.


Managing many sessions

Resume a past session

C-c A s

One picker spans live + saved + new. Live rows are coloured, saved ones dimmed; M-a / M-c / M-p toggle Auggie / Claude / Pi rows, C-u C-c A s expands every saved snapshot.

{dm}Sessions — live + saved  (M-a/M-c/M-p filter · C-u expand):{/}
 {ac}>{/} {g}C{/} {g}●{/} review/auth          {dm}decknix · 2m{/}      {c}#auth{/}
     {y}A{/} {y}◐{/} feature/token-cache   {dm}decknix · now{/}
     {dm}C{/} {dm}·{/}  CONN-473 spike        {dm}decknix · 3d{/}      {c}#spike{/}
     {dm}A{/} {dm}·{/}  proptrack timeout     {dm}nurturecloud · 5d{/}

Selecting a saved row resumes it with its history window restored and input ring rebuilt. C-c A f forks a session instead — the fork keeps the source's tags, workspace and conversation identity.

Search every session

C-c A g

Full-text ripgrep across all transcripts, with the match in context. RET jumps straight to the matching turn.

{dm}Grep all sessions:{/} {c}pubsub timeout{/}
 {gd}proptrack timeout{/}   {dm}nurturecloud · 5d{/}
   {dm}…the {/}{y}pubsub timeout{/}{dm} was an unacked message redelivery …{/}
 {gd}CONN-473 spike{/}      {dm}decknix · 3d{/}
   {dm}…retry policy on a {/}{y}pubsub timeout{/}{dm} should back off …{/}

Never lose a decision — find the conversation where you made it.

Jump to what needs you

C-c A j

Cycle straight to the next session carrying red/amber attention — the agent-shell answer to cmux's "which one needs me?"

{dm}Jump to attention →{/}  {r}◐{/} feature/token-cache  {dm}(verify: “approve test run?”){/}

Repeated presses walk the attention queue: waiting sessions first, then CI failures and review requests.

Batch one prompt to several sessions

C-c A sC-SPCB

Mark rows in the picker with C-SPC, then batch a single prompt to all of them — handy for "rebase onto main" or "add the new lint" across several worktrees.

{dm}Sessions  (C-SPC mark · B batch):{/}
 {g}✓{/} {g}C{/} {g}●{/} review/auth           {rp}decknix{/}
 {g}✓{/} {g}A{/} {g}●{/} feature/token-cache   {rp}decknix{/}
   {dm}A{/} {dm}·{/}  proptrack timeout      {rp}nurturecloud{/}
 {dm}2 marked{/} {ac}· B → send one prompt to both{/}

Marked sessions each receive the composed prompt; results stream back into their own buffers.


Keys used in this tour

KeysDoes
C-c wOpen the *decknix* welcome screen
C-c A wOpen the Agents sidebar (workspace)
C-c A a / C-c A nStart / switch · force a new session
C-c A sSession picker (live + saved + new); M-a/M-c/M-p filter, C-SPC mark
C-c A gGrep across every session
C-c A jJump to the next session needing attention
C-c A eCompose a multi-line prompt
C-c A c r / l / uReview PR · link PR · unlink
C-c x s / h / P / tCopy region as Slack · HTML · PDF · plain
C-c s i / t a / lSession info · add tag · link PR

Inside an agent-shell buffer the A prefix is dropped — C-c s, C-c e, C-c ?. Full reference: Keybindings.

Foundation (Layer 1)

The foundation layer provides the core shell infrastructure that everything else builds on.

Core Components

shell-maker

The underlying comint-like buffer management library. Handles prompt rendering, input submission, scroll behaviour, and process lifecycle. Agent Shell inherits its robust terminal semantics.

ACP (Augment Code Protocol)

The acp package implements the wire protocol between Emacs and the auggie CLI. Unlike HTTP-based integrations, ACP runs auggie as a subprocess — no server, no ports, no latency.

agent-shell.el

The main interface package. Provides:

  • Agent configuration and model selection
  • Session lifecycle (start, interrupt, rename)
  • Mode-line status display
  • Buffer management

Tiered Package Sourcing

Packages are sourced from the most stable channel available:

Priority 1: stable nixpkgs     → (nothing currently — all are too new)
Priority 2: unstable nixpkgs   → shell-maker, acp, agent-shell
Priority 3: custom derivations → agent-shell-manager, workspace, attention

Custom derivations use trivialBuild with pinned GitHub revisions and hashes:

agent-shell-manager-el = pkgs.emacsPackages.trivialBuild {
  pname = "agent-shell-manager";
  version = "0-unstable-2026-03-17";
  src = pkgs.fetchFromGitHub {
    owner = "jethrokuan";
    repo = "agent-shell-manager";
    rev = "53b73f1...";
    hash = "sha256-JPB/OnOhYbM0LMirSYQhpB6hW8SAg0Ri6buU8tMP7rA=";
  };
  packageRequires = [ agent-shell ];
};

As packages mature into nixpkgs, they'll migrate up the priority chain automatically.

Default Behaviour

SettingValueWhy
agent-shell-preferred-agent-config'auggieSkip agent selection prompt
agent-shell-session-strategy'newAlways start fresh; session management via our picker
agent-shell-header-style'textModel/mode in mode-line, not graphical header
agent-shell-show-session-idtShow session ID for resume/history

Welcome Message

Every new session displays a custom welcome with a quick-reference keybinding card:

Welcome to Auggie (opus4.6, agent mode)
────────────────────────────────────────────────────
 Quick Reference

  C-c e     Compose     Open multi-line prompt editor
  C-c s     Sessions    Pick / resume / start session
  C-c q     Quit        Save and quit session
  C-c h     History     View conversation history
  C-c t t   Template    Insert a prompt template
  C-c c c   Command     Pick & insert a slash command
  C-c T t   Tag         Tag this session
  C-c T l   By tag      Filter sessions by tag
  C-c ?     Help        Full keybinding reference
────────────────────────────────────────────────────

The welcome is implemented as an :override advice on agent-shell-auggie--welcome-message, preserving the original auggie welcome while appending the reference card.

Nix Options

programs.emacs.decknix.agentShell.enable = true;  # Enable the entire ecosystem

Disabling this single option removes all agent-shell packages and configuration.

Model Selection

This section covers Auggie, whose model is fully driven by decknix (default, per-quickaction, and per-conversation override re-applied on resume). Claude and Pi select models differently — see Model selection by agent.

Auggie exposes several model families. The framework default is prism-a — Augment's hybrid router that mixes Opus 4.7, Sonnet 4.6, and Gemini Flash per turn (around 28 % cheaper than uniform Opus 4.7 on review-shaped workloads without losing depth where it matters).

TaskRecommendedWhy
PR review (/review-service-pr)prism-aRouter picks Opus on hard diffs, cheaper models on skim — best $/quality tradeoff.
Implementation against a defined specsonnet4.6Mechanical work doesn't need flagship reasoning; ~46 % cheaper than Opus.
Debugging in a familiar codebasesonnet4.6Same — context is local, reasoning is bounded.
Architecture / planningopus4.7Long-horizon reasoning, opinionated codegen — earns its 167 % credit cost.
Triage / classificationhaiku4.5~33 % credit cost; fine for short, well-bounded prompts.
Framework iteration (decknix)prism-aVaried workload — let the router pick.

Override Levers

Three layers, narrowest wins:

  1. Per-sessionC-c C-v inside any agent-shell buffer picks a model for the running conversation; persisted in ~/.config/decknix/agent-sessions.json and re-applied on resume. This is the right lever for one-off task adjustments. The persistence works for every provider; only the resume mechanism differs — Auggie pins it at launch via --model <id>, while Claude/Pi (which take no model launch flag) replay it over ACP (session/set_model) once the resumed session reports ready. See Model selection by agent.

  2. Per-purpose (Nix)programs.emacs.decknix.agentShell.purposes.<name>.{provider,model,mode} pins the provider, model, and permission mode for every launch of a given purpose. Three purposes ship today:

    • pr-review — human-authored PR review (C-c A c r, sidebar Requests row, batch processor). Default: provider = "claude-code", model = "sonnet", mode = "auto".
    • bot-pr-review — PR whose author is a bot (auto-dispatched via the A auto-review toggle, or matched by author heuristic). Default: provider = "claude-code", model = "sonnet", mode = "auto"sonnet rather than the cheapest tier because an unattended auto review needs a model that honours auto.
    • new-session — interactive / QUICK C-c A n. Default: provider = "claude-code", model = null, mode = "auto". Its provider also feeds decknix-agent-default-provider.

    Example — pin PR reviews to Claude with opus for depth, route bot diffs to the cheaper haiku on the same provider, and start new sessions in auto:

    programs.emacs.decknix.agentShell.purposes = {
      pr-review     = { provider = "claude-code"; model = "opus"; mode = "auto"; };
      bot-pr-review = { provider = "claude-code"; model = "haiku"; mode = "auto"; };
      new-session   = { provider = "claude-code"; mode = "auto"; };
    };
    

    Set model = null to defer to the provider's own default (no --model flag is added and no ACP replay runs). Values are validated at daemon start: an unknown provider coerces to decknix-agent-default-provider, and an unknown model drops to nil; both cases log a warning to *Warnings*.

  3. Framework defaultdecknix.cli.auggie.settings.model is written to ~/.augment/settings.json and used when no --model flag is supplied. Org and personal layers can override with lib.mkDefault or plain assignment. Framework defaults for Claude / Pi are provider-native — see Model selection by agent.

Per-conversation overrides set via C-c C-v always win over the purpose and framework defaults. On resume the persisted model is re-applied automatically: for Auggie it flows through decknix--resume-command-build and is appended to the ACP command as --model <id>; for Claude/Pi it is replayed over ACP (session/set_model) by decknix--agent-model-replay-on-ready after the session reports ready.

Purposes that are not pinned via Nix (interactive C-c A n, fork, worktree w s) keep decknix-agent-default-provider with no model pin — they use the provider's own default until you make a per-session choice with C-c C-v.

Permission mode

The session permission mode follows the same narrowest-wins hierarchy as the model, on providers that expose it (today Claude, whose modes are default, auto, acceptEdits, bypassPermissions, and plan):

  1. Per-sessionC-c C-m inside any agent-shell buffer switches the running conversation's mode; the choice is persisted in ~/.config/decknix/agent-sessions.json and re-applied on both resume and fork, so a session left in auto doesn't fall back to per-command permission prompts when you return to it.
  2. Per-purpose (Nix)purposes.<name>.mode seeds the mode for new launches of that purpose. new-session.mode (default "auto") is what fresh C-c A n sessions start on, and it's the fallback for resume/fork when a conversation has no saved mode override.
  3. Provider default — used when no purpose mode is set and the conversation has no override.

Providers without session modes (Auggie, Pi) ignore the mode entirely — it can never break a launch.

Multi-Session (Layer 2)

Layer 2 turns agent-shell from a single-buffer chat into a full session management system.

Unified Session Picker (C-c s)

The session picker combines three sources into one completing-read:

Agent session:
  [new]   Start a new auggie session
  [live]  *agent-shell*<proptrack-fix>
  [live]  *agent-shell*<decknix-docs>
  [saved] a1b2c3d4  2h ago   12x  Investigate proptrack pubsub timeout...
  [saved] e5f6g7h8  3d ago    8x  Refactor agent-shell keybindings... [decknix, refactor]
  • [new] — starts a fresh agent-shell session
  • [live] — switches to an existing Emacs buffer
  • [saved] — resumes a saved auggie session (boots a new agent-shell with --resume <id>)

Saved sessions show: truncated ID, relative time, exchange count, first message preview, and tags (if any).

Session Resume

When you select a saved session, the picker:

  1. Appends --resume <session-id> to the ACP command
  2. Starts a new agent-shell buffer with the auggie session restored
  3. Stores the auggie session ID in a buffer-local variable for history/tagging
;; The resume mechanism — auggie CLI handles the actual session restore
(let ((agent-shell-auggie-acp-command
       (append agent-shell-auggie-acp-command
               (list "--resume" session-id))))
  (agent-shell-start :config (agent-shell-auggie-make-agent-config)))

Session History (C-c h / C-c H)

View the full conversation history for any session:

  • C-c hDWIM: if in an agent-shell buffer with a known session, shows that session's history. Otherwise, prompts to pick.
  • C-c HAlways pick: shows the session picker regardless of current buffer.

History is rendered by generating a share link via auggie session share <id> and opening it in xwidget-webkit (embedded browser) or eww (text browser) as fallback — all inside Emacs.

Clean Quit (C-c q)

Quitting a session:

  1. Prompts for confirmation (y-or-n-p)
  2. Switches to the previous buffer
  3. Kills the agent-shell buffer (sends SIGHUP to auggie, which auto-saves the session)

The session is immediately available in the picker's saved list for future resume.

Buffer Rename (C-c r)

Rename the agent-shell buffer for clarity:

*agent-shell*  →  *agent-shell*<proptrack-pubsub-fix>

Uses agent-shell-rename-buffer from the core package.

Extensions

Manager Dashboard (C-c m)

agent-shell-manager provides a tabulated list of all agent-shell buffers at the bottom of the frame. Toggle with C-c m.

Workspace Tab (C-c w)

agent-shell-workspace creates a dedicated tab-bar tab with a sidebar showing all agent sessions. Toggle with C-c w.

Attention Tracker (C-c j)

agent-shell-attention adds a mode-line indicator showing pending/busy session counts:

AS:2/1    ← 2 sessions pending input, 1 busy

C-c j jumps to the next session needing attention.

Nix Options

programs.emacs.decknix.agentShell = {
  manager.enable = true;    # Tabulated dashboard
  workspace.enable = true;  # Tab-bar workspace
  attention.enable = true;  # Mode-line tracker
};

Productivity (Layer 3)

Layer 3 provides the tools for structured, repeatable interactions with the AI agent.

Compose Buffer (C-c e)

A magit-style multi-line editor for writing prompts. Opens at the bottom of the frame:

┌─────────────────────────────────────────────┐
│  *agent-shell*<my-session>                  │
│                                             │
│  ... conversation ...                       │
│                                             │
├─────────────────────────────────────────────┤
│  Compose prompt → C-c C-c submit, C-c C-k  │
│                                             │
│  Please refactor the UnrecoverableError     │
│  handler to support a new category:         │
│                                             │
│  - Add `TransientNetworkError` subclass     │
│  - Wire it into the metrics recorder        │
│  - Update the Terraform alert definitions   │
│                                             │
└─────────────────────────────────────────────┘
  • C-c C-c — submit the composed text to the agent
  • C-c C-k — cancel and close the compose buffer
  • C-c C-s — toggle sticky (stays open after submit) vs transient (closes after submit)
  • C-c k k — interrupt the agent, C-c k C-c — interrupt and submit
  • Full text-mode editing: RET for newlines, no accidental submissions

Prompt History (M-p / M-n)

The compose buffer supports prompt history across all sessions — cycle through previously sent prompts:

KeyAction
M-pPrevious prompt (older)
M-nNext prompt (newer)
M-rSearch all prompts (consult fuzzy match)

History is loaded lazily from auggie session files — M-p starts with the current session, then progressively loads older sessions as you keep pressing. M-r provides a full fuzzy search across all sessions using consult.

Your current input is saved when you start navigating and restored when you cycle past the newest entry.

Yasnippet Prompt Templates (C-c t t)

Seven built-in templates with interactive fields:

TemplateKeyPurpose
/reviewC-c t t → reviewCode review with focus selector (bugs, performance, security, readability)
/refactorC-c t t → refactorRefactoring with pattern selector (extract, rename, DRY up, etc.)
/testC-c t t → testTest generation covering happy path, edge cases, errors
/explainC-c t t → explainCode explanation with aspect focus
/fixC-c t t → fixBug fix with stack trace placeholder
/implementC-c t t → implementFeature implementation following existing patterns
/debugC-c t t → debugDebugging with logs and steps-taken fields

Templates auto-populate the current file from the source buffer. TAB advances between fields; yas-choose-value provides dropdown selectors.

Creating Templates

  • C-c t n — create a new yasnippet template
  • C-c t e — edit an existing template

Templates are stored in ~/.emacs.d/snippets/agent-shell-mode/.

Custom Slash Commands (C-c c c)

Slash commands are markdown files with YAML frontmatter, deployed to ~/.claude/commands/ (the shared location read by both Claude Code and Auggie) and fanned out to ~/.pi/agent/prompts/ so Pi picks them up as /name prompt templates too:

---
description: Create a new session from a Jira ticket key
argument-hint: [session name or JIRA-KEY]
---

Create a new Augment session and rename it in one step.
...

Built-in Commands

CommandDescription
/startCreate a new session from a Jira ticket key or plain name
/find-sessionSearch all saved sessions by keyword (up to 500)
/pivot-conversationHard pivot — discard current plan, re-evaluate
/step-backStop, summarise progress, wait for direction

Command Discovery

The picker (C-c c c) scopes discovery to the agent the current session is running as: a Claude/Auggie session lists ~/.claude/commands/ (plus project-level .claude/commands/ and the legacy ~/.augment/commands/ during the transition), while a Pi session lists ~/.pi/agent/prompts/. Invoked outside an agent session, it falls back to scanning the union so everything is still discoverable. Duplicates collapse (preferring the canonical global copy), and each command shows its scope and description:

Command:
  /start           (global)  Create a new session from a Jira ticket key
  /find-session    (global)  Search all saved sessions by keyword
  /pivot-conversation (global)  Hard pivot — discard current plan
  /deploy-check    (project) Verify deployment prerequisites

Nix-Managed vs Runtime Commands

Nix-managed commands are deployed as symlinks. User-created commands are regular files. On decknix switch, Nix refreshes its symlinks; runtime-created commands persist untouched.

Session Tagging (C-c T)

Tag sessions with freeform labels for organisation:

KeyAction
C-c T tAdd a tag (with completion from existing tags)
C-c T rRemove a tag
C-c T lFilter sessions by tag → resume picker
C-c T eRename a tag across all sessions
C-c T dDelete a tag globally
C-c T cCleanup orphaned tags (sessions that no longer exist)

Tags are stored in ~/.config/decknix/agent-sessions.json, keyed by auggie session ID. The session picker shows tags inline:

[saved] a1b2c3d4  2h ago  12x  Fix pubsub timeout... [proptrack, bug]

Quick Actions

PR Review (C-c c r / C-c A c r)

Start a code review session for a GitHub PR:

  1. Prompts for PR URL (auto-detects from clipboard)
  2. Creates a named session: Review: owner/repo#123
  3. Tags the session with review and sends /review-service-pr <url>

Batch Process (C-c c B / C-c A c B)

Launch multiple sessions from a single editor — ideal for batch code reviews or parallel investigations:

┌─────────────────────────────────────────────┐
│  Batch: C-c C-c submit | C-c C-k cancel    │
│                                             │
│  # Batch session launcher — workspace: ~/.. │
│  --- backend : ~/Code/my-project            │
│  https://github.com/org/api/pull/42         │
│  https://github.com/org/api/pull/43         │
│                                             │
│  --- frontend                               │
│  https://github.com/org/web/pull/17         │
│                                             │
└─────────────────────────────────────────────┘

Syntax:

  • --- <name> [: <workspace>] — group header (items below launch as one session)
  • Ungrouped URLs — each gets its own session
  • # lines — comments, ignored

Snippets (via yasnippet in batch compose mode):

KeySnippetDescription
---Group header--- <name> : <workspace> with tab stops
prPR URLGeneric github.com/<owner>/<repo>/pull/<number>

Org-specific snippets (e.g., pre-filled workspace paths) can be added via downstream decknix-config.

C-c C-c parses the buffer and launches all sessions. A summary buffer shows success/failure for each.

Auto-Review (T → Requests → A)

Automatically dispatch a review session when a PR review request that @-mentions you arrives in the hub Requests section — so a verdict is waiting by the time you switch to it.

Cycle the mode from the sidebar Toggles transient (T → Requests → A):

StateBehaviour
offDisabled (default).
bot+@Auto-review bot-authored PRs that @-mention you, via /review-and-ship-bot-pr.
human+@Auto-review human-authored PRs that @-mention you, via the background /review-service-pr-factory.
any+@Both — bots ship, humans get the background review.

Every active state requires the @-mention: this is a deliberate safety guard so team-noise PRs (where you are not directly addressed) never spawn a session. Human reviews run as a background factory session whose verdict surfaces through the attention indicator (AS:n/m, jump with C-c j).

Incoming-only: turning the toggle on seeds the current backlog as already-handled, so only genuinely new mentioned PRs dispatch — enabling it never floods you with sessions for the existing queue. A live-session guard plus a per-PR dedup set prevent double-dispatch.

Per-workspace commands: override the slash command per repository workspace via decknix-auto-review-commands (an alist of (workspace . (:review CMD :ship CMD))); the global defaults are decknix-auto-review-default-review-command and decknix-auto-review-default-ship-command.

Focus Steal (T → Global → g, default off)

Off by default, Emacs never steals window focus. Opt in to have the frame raise itself to the foreground when work needs your attention — handy when agent sessions run in the background behind other apps.

Cycle the mode from the sidebar Toggles transient (T → Global → g, or the footer focus label):

StateBehaviour
offNever raise the frame (default).
attentionRaise the frame when a backgrounded session enters a waiting / needs-input state.
bothAlso raise the frame when a new session is created (e.g. an auto-review dispatch).

The attention raise is edge-triggered (once per transition into waiting) and skipped when you are already viewing that session. On the background Emacs daemon an osascript … activate is issued so macOS actually brings the app forward.

Navigation: C-c A o jumps to the sidebar window from anywhere; C-c A j jumps the other way, to the next session needing input. These cover session↔sidebar movement whether or not focus-steal is enabled.

Priority View (C-c A p, opt-in)

A ranked, lane-based "what should I do next?" view over the hub's existing data, shown in a standalone read-only *Agent Priority* buffer. Off by default; enable it with programs.emacs.decknix.agentShell.hub.priority.enable = true to bind C-c A p.

Four ordered lanes, highest-leverage first:

LaneWhat it surfaces
DiscussionsPRs where a human is awaiting your reply (highest priority).
ReviewsPRs awaiting your review verdict.
TasksYour non-done Jira issues.
QueueYour open WIP PRs flowing through the pipeline.

Within each lane, items you were directly @-mentioned on rank first, then oldest first. In the buffer: RET opens the item, g refreshes, q quits.

This is phase 1 (existing sources only). A sidebar live-view mode, pin / exclude support, and generic GitHub Actions pipeline enrichment of the Queue lane are tracked in issues #142 and #141.

Formatting Output

The agent emits raw markdown, which the comint buffer shows literally — collapsed tables and **bold** / ### head / [t](u) syntax that pastes badly into Slack and other tools. Three complementary tools address this.

Auto-aligned Tables (default on)

Tables in agent-shell output are automatically aligned via display overlays — the columns line up visually while the underlying buffer text stays raw markdown, so M-w and Copy Region As… still see the original. When an aligned table would be wider than the window it reflows into the bullet list shown below instead. The same overlay is enabled in inline-review buffers. Disable with programs.emacs.decknix.agentShell.tableOverlay.enable = false; to fall back to the on-demand command only.

Reformat Table (C-c xt)

Re-aligns the GFM table at point (or in the active region) so the pipes line up:

| Name  | Age | City |
| ----- | --- | ---- |
| Alice | 30  | NYC  |

When the aligned table would be wider than the window, it reflows into a per-row bullet list with key/value sub-items instead — readable even in a narrow sidebar:

• Alice
    - Age: 30
    - City: NYC

Copy Region As… (C-c x)

Select a region, then C-c x opens a transient that converts it and puts the result on the kill-ring in the chosen syntax:

KeyFormatNotes
mMarkdowntables re-aligned, prose untouched
sSlack mrkdwn*bold*, _italic_, ~strike~, <url|text>, headings → bold line, &/</> escaped, tables → aligned code block
hHTMLvia pandoc (GFM → HTML)
pPlain textemphasis stripped, links → text (url), tables aligned

It also has an Export region to file entry that writes a file rather than copying to the kill-ring:

KeyFormatNotes
PPDFvia pandoc; prompts for a path, then offers to open it

Both the HTML and PDF paths need pandoc, and PDF additionally needs a PDF engine on PATH. decknix installs both by default — pandoc plus typst (small, fast, no TeX) — so C-c x h / C-c x P work out of the box after decknix switch. See the Nix options below to change the engine or opt out. If no engine is found the command reports which to install (the auto-detect order is typst, tectonic, weasyprint, wkhtmltopdf, xelatex, pdflatex) rather than failing silently.

C-c x is bound in agent-shell buffers and in markdown / review buffers (C-c y is reserved for yasnippet). The Slack mapping follows Slack's mrkdwn spec.

Switching Agents / Migrating off Auggie

decknix supports three agent providers — Auggie (A), Claude (C), and Pi (P) — and new sessions default to Claude. There are three distinct operations, and it's worth knowing which one to reach for:

GoalCommandCrosses agents?
Continue the same conversation on the same agentResume — picker C-c A s (Previous / Saved), or restart C-c s RNo
Continue a discussion on a different agent (Auggie → Claude / Pi)ForkC-c A f / C-c s fYes
Change model mid-conversation (same agent)C-c C-vNo
Change permission mode mid-conversation (same agent)C-c C-mNo

There is no "clone" command — fork is the cross-agent path.

Why resume can't switch agents

Resume is provider-native: each agent stores its transcripts in its own directory and format, and resume relaunches that agent with --resume <id>:

  • Auggie → ~/.augment/sessions/*.json
  • Claude → ~/.claude/projects/*.jsonl
  • Pi → ~/.pi/sessions/

The picker reads the session metadata and always restores the original provider, so an Auggie transcript can't be replayed as Claude.

Fork hands off context to the new agent

C-c A f (decknix-agent-session-fork) is the tool for "I'm moving this work off Auggie":

  1. Prompts for the new provider — pick Claude or Pi.
  2. Pre-seeds the source session's workspace and tags (editable before you confirm).
  3. Auto-sends a context hand-off as the new session's first message, naming the source provider, session id, and best-effort transcript path — so the new agent can read the prior conversation and pick up where you left off.

This is best-effort continuity (the new agent reads the named transcript file to reload context rather than a true session port), but for "continue this discussion in Claude" it's the intended button. Invoked outside an agent-shell buffer there's no source, so fork degrades to a plain new session.

Model selection by agent

C-c C-v (set session model) isn't Auggie-only. It's the upstream agent-shell verb that lists whatever models the running agent's ACP bridge advertises and switches the live session to your choice via an ACP session/set_model request. decknix persists that choice against the conversation for every provider, and restores it on resume — the only difference is the mechanism:

  • Auggie pins the model at launch via its --model <id> flag, so the resumed conversation comes up on the right model immediately.
  • Claude / Pi don't accept a model launch flag, so decknix instead replays the saved model over ACP (session/set_model) the moment the resumed session reports ready — the same lever C-c C-v uses live. The result is the same: your per-conversation model survives the resume.
AgentSwitch mid-sessionRestored on resume?Set the default
AuggieC-c C-v✅ yes — --model at launchdecknix.cli.auggie.settings.model
ClaudeC-c C-v✅ yes — ACP set_model replayagent-shell-anthropic-default-model-id, or ANTHROPIC_MODEL env
PiC-c C-v if Pi's bridge advertises models✅ yes — ACP set_model replayPi's own config (~/.pi.json)

The default still matters for the first turn of a brand-new conversation, before you've made any C-c C-v choice to persist — set it so fresh sessions start on the right model.

Automated purposes — for PR reviews (C-c A c r, sidebar Requests row, auto-review dispatch), pin both the provider and model per purpose via programs.emacs.decknix.agentShell.purposes.<name>.{provider,model} in your Nix config. See Per-Purpose Provider & Model for the full list of purposes, defaults, and validation semantics. Purpose pins survive across all three providers — the model rides --model on launch for Auggie and is replayed over ACP for Claude / Pi.

Claude — set the per-Emacs default in your personal config so every new Claude session starts on the right model (after that, C-c C-v choices persist and are replayed on resume):

(with-eval-after-load 'agent-shell-anthropic
  (setq agent-shell-anthropic-default-model-id "claude-sonnet-4-5"))

To point at a custom or proxy endpoint/model instead, use the environment:

(setq agent-shell-anthropic-claude-environment
      (agent-shell-make-environment-variables
       "ANTHROPIC_MODEL" "..."))

Pi — the default model is governed by Pi itself (its own config / in-session controls); decknix wires no default-model-id for Pi. But once you pick a model with C-c C-v it's persisted and replayed on resume like Claude. If C-c C-v reports "No session models available", the Pi ACP bridge isn't advertising a model list — there's nothing to switch or persist, so select the model through Pi's own configuration instead.

Permission mode selection

C-c C-m (set session mode) is the permission-mode analogue of C-c C-v. It lists whatever modes the running agent advertises and switches the live session over ACP. Today only Claude exposes session modes — default (prompt each time), auto (Claude auto-approves tool use, including shell commands, via its own classifier), acceptEdits (auto-accept file edits, still prompts for commands), bypassPermissions (no prompts — unsafe), and plan (read-only). Providers without session modes (Auggie, Pi) ignore it.

decknix persists your choice against the conversation and re-applies it on both resume and fork, so a session you switched to auto stays on auto when you come back to it — no more re-approving commands after every resume. A fork inherits its parent conversation's mode. When a conversation has no saved mode override, resume/fork fall back to the new-session purpose default (below).

The default for brand-new sessions is the new-session purpose's mode, which ships as "auto". Override it (or the per-review-purpose modes) in Nix:

programs.emacs.decknix.agentShell.purposes = {
  new-session   = { mode = "auto"; };        # default for C-c A n
  pr-review     = { mode = "auto"; };
  bot-pr-review = { mode = "auto"; };
};

See Per-Purpose Provider & Model for the full purpose list and validation semantics (an unknown mode, or one for a provider without session modes, drops to nil at boot with a warning).

Notes

  • Start fresh on Claude with plain C-c A n (it prompts for provider; C-u C-c A n skips the prompt and uses the default, Claude).

Nix Options

programs.emacs.decknix.agentShell = {
  templates.enable = true;  # Yasnippet prompt templates
  commands.enable = true;   # Nix-managed slash commands
  hub.priority.enable = false;  # opt-in Priority view (C-c A p)
  tableOverlay.enable = true;   # auto-align GFM tables via display overlays

  # Copy-as-format / export runtime deps (C-c x h / C-c x P)
  copyRegion.pandoc.enable = true;  # install pandoc (HTML + PDF)
  copyRegion.pdfEngine = "typst";   # "typst"|"tectonic"|"weasyprint"|"wkhtmltopdf"|null
};

programs.emacs.decknix.ui.focus.steal = "off";  # "off" | "attention" | "both"

Integration (Layer 4)

Layer 4 connects the agent shell to external tools and services via the Model Context Protocol (MCP).

MCP Server Configuration

MCP servers extend the agent's capabilities by providing access to external data sources and APIs. Decknix manages them declaratively:

{ ... }: {
  decknix.cli.auggie.mcpServers = {
    context7 = {
      type = "stdio";
      command = "npx";
      args = [ "-y" "@upstash/context7-mcp@latest" ];
    };
    "gcp-monitoring" = {
      type = "stdio";
      command = "npx";
      args = [ "-y" "gcp-monitoring-mcp" ];
      env.GOOGLE_APPLICATION_CREDENTIALS = "~/.config/gcloud/credentials.json";
    };
    "nurturecloud-knowledge-base" = {
      type = "stdio";
      command = "npx";
      args = [ "-y" "nurturecloud-kb-mcp" ];
    };
  };
}

Viewing Servers (C-c A S)

The MCP server listing shows all configured servers in a formatted buffer:

MCP Server Configuration
════════════════════════════════════════════════════════
Source: ~/.augment/settings.json

  context7
    type:    stdio
    command: npx
    args:    -y @upstash/context7-mcp@latest

  gcp-monitoring
    type:    stdio
    command: npx
    args:    -y gcp-monitoring-mcp
    env:
      GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/credentials.json

════════════════════════════════════════════════════════
Runtime changes (auggie mcp add) are temporary.
To persist, edit Nix config and run decknix switch.
Press q to close this buffer.

Declarative vs Runtime

The two-tier model:

  1. Nix-managed (persistent) — defined in your Nix config, deployed on decknix switch. This is the baseline.
  2. Runtime (temporary) — added via auggie mcp add during a session. Lost on next decknix switch.

This lets you experiment with new MCP servers without committing to them, while ensuring your team's standard servers are always present.

How MCP Enhances the Agent

With MCP servers configured, the agent can:

ServerCapability
context7Query up-to-date library documentation
gcp-monitoringSearch GCP logs, error groups, Datastore entities
nurturecloud-knowledge-baseSearch resolved Jira tickets and internal docs
jiraRead/create/transition Jira issues
confluenceSearch and create Confluence pages
githubFull GitHub API access (issues, PRs, code search)

The agent automatically discovers available MCP servers and uses them when relevant to the conversation.

Organisation-Specific Servers

Org configs can layer additional MCP servers on top of the framework defaults. See Organisation Configs for how this works.

For NurtureCloud-specific MCP server configuration, see the NC Agent Shell Workflows documentation.

Context Awareness (Layer 5)

Layer 5 makes the agent shell work-aware — it passively tracks the issues, PRs, CI status, and review threads relevant to your current conversation.

How It Works

┌─────────────────────────────────────────────────────────────┐
│ Issues: #51 #52  |  PR: #50  |  CI: ✅  |  Reviews: 2 unresolved │
├─────────────────────────────────────────────────────────────┤
│  *agent-shell*<cherries-epic>                               │
│                                                             │
│  Let's work on the migration wizard (#52). The PR (#50)     │
│  is passing CI now. There are 2 unresolved review threads.  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

The header-line at the top of every agent-shell buffer shows a live summary of tracked context.

Auto-Detection

The context panel scans buffer text for references:

PatternDetected AsExample
#123GitHub issue/PR (current repo)#51
org/repo#123GitHub issue/PR (specific repo)ldeck/decknix#52
PROJ-1234Jira ticketALR-4268, NC-7801

False positives are filtered: HTTP-200, SHA-256, UTF-8, ISO-8601 are excluded.

Data Fetching

For each detected GitHub reference, the panel fetches metadata via gh CLI:

  • Issues/PRs: number, title, state (open/closed/merged), URL, type (issue vs PR)
  • CI: latest run status for the current branch (pass/fail/running)
  • Reviews: unresolved review thread count across open PRs in context

CI status auto-polls every 60 seconds.

Header-Line Indicators

IndicatorMeaning
Issues: #51 #52Tracked issues (green = open, grey = closed)
PR: #50Tracked PRs (green = open, purple = merged, red = closed)
CI: ✅Latest CI run passed
CI: ❌Latest CI run failed
CI: 🔄CI run in progress
Reviews: 2 unresolvedUnresolved PR review threads (yellow warning)

Context Panel (C-c I)

The full detail panel shows everything in a formatted buffer:

Agent Context Panel
────────────────────────────────────────────────────

Issues
────────────────────────────────────────
  #51          Cherries epic — high-appeal features    open
  #52          Migration wizard                        open 📌

Pull Requests
────────────────────────────────────────
  🟢 #50       Agent shell context awareness           open

Branch & CI
────────────────────────────────────────
  Branch: feature/context-panel  (ldeck/decknix)
  CI:     ✅ success  CI / Build and Test

Reviews
────────────────────────────────────────
  4 threads, 2 unresolved

────────────────────────────────────────────────────
Press q to close.  C-c i g to open item in browser.

Pin / Unpin

Manually pin items to keep them in context even if they're not mentioned in the conversation:

KeyAction
C-c i aPin an issue/PR (e.g., #49, NC-1234, org/repo#12)
C-c i dUnpin — remove from tracked context

Pinned items are marked with 📌 in the detail panel.

KeyAction
C-c i iList tracked issues (completing-read → open in browser)
C-c i pList tracked PRs
C-c i cRefresh and show CI status
C-c i rRefresh and show review thread count
C-c i gOpen any tracked item in external browser
C-c i fVisit in magit-forge

Persistence

Pinned context items are saved per-session in ~/.config/decknix/agent-sessions.json (the same file used for tags). When you resume a session, pinned items are restored and metadata is re-fetched.

Nix Options

programs.emacs.decknix.agentShell.context.enable = true;

Requires gh CLI on $PATH (included in decknix default packages).

On-Support (DoS) Features

The agent-shell ships three in-editor surfaces that turn a team's "developer-on-support" (DoS) playbook from a static page you read into a live environment you run: a prioritised board, a filterable dashboard, and a guided checklist. They give a support engineer magit-like single-key actions over the live worklist — browse, investigate, spawn an agent, export the day's audit — without leaving Emacs.

Org engine required. These surfaces are framework code, but the data and the actions come from an org-provided CLI (the "engine") that reads your issue tracker and computes the runbook priority order. NurtureCloud ships one as nc-dos (see the org config's TechOps On-Support page). Point the decknix-dos-board-cli / decknix-support-dashboard-* variables at your own engine to light these up for another org.

The three surfaces (complementary)

SurfaceCommandWhat it isReach for it to…
Priority boardC-c A BThe cockpit — the runbook's ranked worklist (incidents → alerts → tasks) with one-key actions per rowWork top-down: land on the next item and act
Support dashboardC-c A DThe reference view — the full board + alert feed, grouped by status and filterableSurvey / slice by user, category, or service; draft the daily log
Guided workflowC-c A WThe map — the day-aware "what to do, when, how" checklistKnow the order of the day and tick tasks off

All three are read-only, deterministic (no LLM), and refresh only while visible, so they are cheap to leave open all day. They are backed by the same engine, so the board here and the terminal console stay in parity.

The DoS priority board — C-c A B

The living playbook. It renders the engine's computed priority model as ranked lanes and stamps each row with its ticket key, so single-key actions operate on the item at point. The header is the constant-attention surface: weekday, deploy/freeze posture, weekly-report freshness, and live open-work counts.

KeyAction
RET / oBrowse the ticket at point
iSpawn a foreground agent (a new agent-shell session, runbook-primed) on the item
xSpawn a background agent (headless, logged to the engine's runs dir)
cCopy the exact fg/bg spawn commands for the item to the kill-ring
n / pMove to the next / previous item (TAB / S-TAB also)
rOpen the current weekly report
WExport today's support worksheet (live counts) and open it in Emacs
gRefresh now
? / .Action menu (magit-style transient)
qBury the board

The support dashboard — C-c A D

The survey view: the DoS board grouped by status alongside the alert feed, with filtering. Best when you need to slice the board a different way (everything one person owns, one service, one category) or draft the day's report entry.

KeyAction
b / RETBrowse the issue at point
/Filter by status / category / user / service
\Clear all filters
aAssign the issue at point
iInvestigate with a foreground agent
AInvestigate as an alert (alert-specific prompt + pre-comment gate)
pShow the engine's priority panel (text)
xSpawn a background agent on the issue
cPrint / copy the spawn command
tToggle auto-spawn of background agents
rOpen the weekly report
RDraft today's daily-log entry from the live board
wOpen the guided workflow
gRefresh
? / .Action menu
qBury

The guided workflow — C-c A W

The day-aware checklist distilled from the playbook work order (daily checks, scheduled deploys, audits, then the priority ladder). It cross-links into the other surfaces so you can jump from a step to the live view.

KeyAction
RETRun the action for the step at point (e.g. open service dashboards)
oOpen the link for the step at point
SPCToggle the step done
n / pNext / previous step
aJump to alert triage (dashboard + the tracker's alert swimlane)
bOpen the tracker's DoS board in the browser
dOpen the support dashboard (C-c A D)
POpen the playbook page
gRefresh
?Action menu

Step-by-step: a support day

  1. Start with the map. C-c A W — read the day's order: production incidents first, then daily checks, scheduled deploys (on the stipulated days), audits, then the work ladder. Tick items with SPC as you go.
  2. Do the daily checks. From the workflow, RET on Service Health opens the service dashboards; run Build Health per your playbook. Note anything found for the report.
  3. Live in the cockpit. C-c A B — the board draws the ranked worklist. Work top-down:
    • Land on the top incidentRET to open it; incidents preempt everything.
    • Move to an alert (n) → i to open a runbook-primed agent-shell session that triages it (respecting the pre-comment gate), or x to run it in the background.
    • Pick up a DoS task the same way; c first if you want to see/copy the exact command.
  4. Slice when needed. C-c A D — filter (/) by user, category, or service to reconcile the full board, or draft the daily log with R.
  5. Export the audit. Back on the board, W writes today's worksheet (seeded with live counts) and opens it in Emacs — review it and paste the entry into the weekly report (r opens the report). The report is an export surface for the day's work, kept current as you go.
  6. Parity outside Emacs. The same engine runs in a plain terminal for teammates who don't use Emacs — see From the terminal below.

From the terminal (CLI parity)

Every board action is delegated to a CLI, so the whole flow is available without Emacs. Two CLIs are relevant:

  • The org priority engine (org-provided; NurtureCloud ships nc-dos): nc-dos-sidebar is the single-key priority console the board renders, nc-dos-sidebar --once prints the panel, --json feeds the board, and nc-dos-worksheet exports the day's worksheet. See your org config's TechOps On-Support page for the full command set.

  • decknix session — the framework's workspace- and tag-aware session CLI. The agents the board spawns on a ticket land as ordinary sessions, so you can list, resume, tag, or start them from the terminal and they stay in sync with the sidebar:

    # Resume the latest session you spawned on a DoS item (by tag)
    decknix session resume --tag dos
    
    # Start a session on a ticket, seeded and pre-tagged
    decknix session new --tag dos -- "Investigate ALR-5752"
    
    # List Claude sessions touched today, as JSON
    decknix session list --agent claude --since 1d --json
    

How it fits together

There is one engine and several faces. The engine (org-provided) reads the tracker, computes the runbook priority order, and owns every write (agent spawns, tab opens, tick state). The board and dashboard render that model and delegate actions back to it, so the Emacs and terminal experiences never drift apart and no runbook logic is duplicated in Elisp.

Keybindings Reference

All agent-shell keybindings are available in two forms:

  • In-buffer: C-c <key> — short form, only inside agent-shell buffers
  • Global: C-c A <key> — works from any buffer

The C-c A prefix is labelled "Agent" in which-key.

Session Management

In-bufferGlobalAction
C-c sC-c A sSession picker (live + saved + new)
C-c A gGrep sessions (full-text search across all history)
C-c qC-c A qQuit session (saves automatically)
C-c hC-c A hView history (current session or pick)
C-c HC-c A HView history (always pick)
C-c rC-c A rRename buffer
C-c A aStart / switch to agent
C-c A nForce new session
C-c A kInterrupt agent
C-c bC-c A bSwitch agent buffer (live only) — MRU order, status-coloured

In-Picker Keys

Every session-facing picker (C-c A s, C-c A b, C-c A g) prefixes each row with a provider glyphA Auggie, C Claude, P Pi — and shares a set of picker-local action keys:

KeyAction
M-a / M-c / M-pToggle visibility of Auggie / Claude / Pi rows (filter is shared across all three pickers; not persisted)
M-wToggle workspace filter (all workspaces ↔ the calling buffer's workspace); active filter shows in the prompt as [~/path/to/ws]
C-SPCMark row for batch action
C-kKill highlighted live session buffer(s)
C-dDelete saved / previous session from disk and metadata
C-uExpand (per-picker; e.g. C-u C-c A s shows every saved snapshot instead of one-per-conversation)

Input & Editing

KeyAction
C-c e / C-c A eCompose buffer (multi-line editor)
RETSend prompt (at end of input)
S-RETInsert newline in prompt
C-c C-cInterrupt running agent
C-c EInterrupt agent and open compose buffer
TABExpand yasnippet template

In Compose Buffer

KeyAction
C-c C-cSubmit composed prompt
C-c C-kCancel / close compose buffer
C-c C-sToggle sticky (stays open) vs transient
C-c k kInterrupt agent
C-c k C-cInterrupt agent and submit
M-pPrevious prompt (history)
M-nNext prompt (history)
M-rSearch prompt history (consult)

Templates (C-c Y / C-c A t)

In-buffer, snippet insertion is handled by the upstream C-c Y ("+snippet") prefix — no decknix-specific in-buffer binding. The agent-namespaced C-c A t global prefix is preserved for explicit, namespaced access.

KeyAction
C-c YSnippet prefix (upstream) — insert / new / visit
C-c A t tInsert a prompt template
C-c A t nCreate new template
C-c A t eEdit existing template

Commands (C-c c / C-c A c)

KeyAction
cPick & insert a slash command
nCreate new command
eEdit existing command
rReview PR by URL (quick action; launches in the pr-review purpose's auto mode)
BBatch process (multi-session launcher)
lLink PR to session
LLink repo+branch to session (direct-push repos)
uUnlink PR or repo (single picker)

Tags

Conversation-scoped tags (add / remove / list for this session) are now nested under the session sub-prefix at C-c s t. Global tags (rename / delete / cleanup across all sessions) remain at C-c A T.

Conversation-scoped (C-c s t)

KeyAction
aAdd tag (create or select)
rRemove tag
lList this session's tags

Global (C-c A T)

KeyAction
tTag current session
rRemove tag
lList / filter by tag
eRename a tag
dDelete tag globally
cCleanup orphaned tags

Trigger sidebar transients without switching focus away from the agent-shell buffer. C-c W opens decknix-sidebar-transient — the same parent menu that the sidebar's ? / h opens — exposing Navigate / Quick / Actions plus T for the toggles sub-transient.

KeyAction
C-c WOpen sidebar action transient
C-c W TToggles transient (filters, sort, indicators)
C-c wToggle the workspace tab itself (unchanged)

Model & Mode

In-bufferGlobalAction
C-c C-vPick model (persists per-conversation; survives resume)
C-c C-mPick permission mode (persists per-conversation; survives resume/fork)

See Model Selection for the recommended-model-by-task table and the per-purpose (programs.emacs.decknix.agentShell.purposes) / framework (decknix.cli.auggie.settings.model) override levers.

Context (C-c i / C-c A i)

KeyAction
iList tracked issues
pList tracked PRs
cShow CI status
rShow review threads
aPin issue/PR to context
dUnpin from context
gOpen in browser
fVisit in forge
In-bufferGlobalAction
C-c IC-c A IFull context panel

Extensions

In-bufferGlobalAction
C-c mC-c A mManager dashboard toggle
C-c wC-c A wWorkspace tab toggle
C-c jC-c A jJump to session needing attention
C-c A SMCP server list

Help

In-bufferGlobalAction
C-c ?C-c A ?Full keybinding reference (this page, in Emacs)

How It Compares

The Agent Shell is not another terminal multiplexer, IDE, or cloud agent — it is an editor-native coordination framework. This page is a candid assessment, not a scorecard: it compares the Agent Shell with the tools teams reach for when running AI coding agents to find (a) where those tools have better ideas worth borrowing, (b) where decknix genuinely leads, and (c) the gaps nobody fills yet that decknix is positioned to bridge. They solve overlapping problems on very different substrates.

Legend: ✅ native / first-class · ◐ partial or via workflow · ✗ absent

Competitor features move quickly. This snapshot was compiled July 2026; verify specifics against each tool's current docs (linked below).

The tools, in one line each

ToolWhat it fundamentally is
decknix / deckmacs Agent ShellAn Emacs-native, Nix-configured framework that coordinates multiple AI agents (over ACP) and ties their work to your project's real state — issues, PRs, CI, worktrees.
cmuxA native macOS terminal multiplexer (Ghostty) that turns parallel agents and their sub-agents into panes/splits with attention rings.
supacodeA native macOS "command center" app (libghostty) for running 50+ CLI agents in parallel, each in its own worktree.
CursorAn AI IDE; its Agents Window launches up to 8 parallel agents, each in an isolated worktree, emitting PRs.
OpenAI CodexA multi-surface agent (CLI + IDE + web + cloud) on one execution model; runs parallel cloud tasks in sandboxes and proposes PRs.
Augment IntentA web workspace for agent orchestration: a coordinator drafts a living spec, implementor agents run in parallel worktrees, a verifier checks.
Claude CodeA multi-surface agent (terminal · IDE · desktop · web) with subagents, agent-teams, worktrees and an agent view dashboard — one of the agents decknix drives, not a competitor to it.

Substrate & parallelism

CapabilityAgent ShellcmuxsupacodeCursorCodexIntentClaude Code
Runs inside your existing editor✅ Emacs✗ terminal✗ app◐ its own IDE◐ IDE/CLI✗ desktop app◐ CLI/IDE ext
Multi-vendor agents (not one model)✅ ACP: Claude Code · Auggie · Pi · Gemini✅ any TTY agent✅ any CLI agent◐ Cursor-managed✗ OpenAI only✅ BYOA✗ Anthropic
Run many agents in parallel◐ many sessions + sub-agents✅ panes✅ 50+✅ up to 8✅ cloud queue✅ roles✅ background agents
Sub-agents made visible◐ shown (status: roadmap)✅ as panes✗ independent, not nested✅ Agents Window✅ collected results✅ coord/impl/verify✅ agent view / teams
Auto worktree-per-agent isolation◐ worktree-aware◐ scripted✅ native--worktree

The right-hand tools win the "fan out eight agents and race them" game — decknix's model is deliberately one focused session per unit of work, coordinated and tracked, with parallelism through additional sessions, provider sub-agents, and the pr-implementer / pr-shepherd workflow. Fan-out-per-worktree is a roadmap direction (see Sidebar Layouts → WIP), not the current centre of gravity.

Coordination, provenance & attention

CapabilityAgent ShellcmuxsupacodeCursorCodexIntentClaude Code
Persistent sidebar / overview✅ Requests · WIP · Live · Sessions✅ tabs✅ worktree list✅ Agents Window✅ app 3-pane✅ workspace✅ agent view
Attention state as colour✅ red/amber/green tied to CI · review · age✅ blue "needs you" ring✅ busy / awaiting / idle badges✅ push / iOS✅ app + iOS◐ verifier✅ agent-view state icons
PR create / review✅ link · review mode · hub reviews◐ shows PR # / status✅ GitHub-native✅ emits PRs✅ proposes PRs✅ commits · PRs · @claude review
Cross-service provenance — Jira · Confluence · CI · Slack✅✅ hub aggregates Jira · Confluence · GitHub · TeamCity CI (Slack / email / data / support: roadmap)✗ git/PR only✗ GitHub/CI◐ Slack · Linear · GitHub triggers◐ Linear · Slack · Jira (MCP)◐ Context Engine (MCP)◐ Jira · Slack · Linear (MCP)
Timeline / history of what happened◐ session history · grep · nav (full timeline: roadmap)✗ live panes only◐ living spec
Per-conversation resource tracking (PRs authored vs reviewed, tags, worktree, model, mode)✅✅◐ per-agent◐ per-task◐ per-spec◐ per-session

This is the row that matters most — and note the honest ◐s: several tools now surface attention and can act on Jira/Slack/Linear through MCP connectors. But they reach each service on demand, per connector. Agent Shell instead aggregates and attributes that external state: the hub polls Jira, Confluence, GitHub and CI into one store; the progress layer rolls it up into a red/amber/green attention model; and per-conversation linking attributes PRs (authored vs. reviewed), worktrees and tickets to the session that produced them — answering not just "which agent needs me?" but "what has this session actually done, across every service, and when?"

Openness & extensibility

Agent ShellcmuxsupacodeCursorCodexIntentClaude Code
Config-as-code✅✅ Nix — declarative, reproducible, org-shareable◐ config.toml◐ CLAUDE.md / settings
Extension surface✅ Elisp + Nix options + MCP✅ socket API / CLI / skills◐ CLI / deeplinks◐ extensions / MCP✅ SDK / MCP / skills / Action◐ MCP / Context Engine✅ SDK / MCP / hooks / skills
Open source✅ GPL-3.0◐ source-available (FSL)◐ CLI only (Apache-2.0)✗ proprietary
Cost modelFree / OSSFree / OSSFree betaPaid IDESubscription / APIPaid (credits)Subscription / API
Session persistence / resume✅✅ resume + fork carry model, permission-mode, tags◐ contested✅ zmx reattach✅ resume + fork✅ resume + fork

Where it genuinely leads

Pulling the matrix together, four things are rare or absent everywhere else — these are real strengths today, not aspirations:

  1. It lives in your editor. Every other tool is a terminal, an app, a web workspace, or its own IDE. Agent Shell is a first-class Emacs citizen — agents sit next to your buffers, magit, and org files, driven by the same keybinding muscle memory. (See Keybindings.)
  2. Config-as-code, org-shareable. Providers, models, MCP servers, sidebar layout, and review defaults are declared in Nix and rolled out with decknix switch — reproducible per machine and inheritable across a team's org config. No other tool here ships its coordination setup as version-controlled, composable configuration. (See Integration.)
  3. Vendor-neutral by protocol. ACP lets one interface drive Claude Code, Auggie, Pi, or Gemini — and pin a different model/mode per purpose (e.g. Opus for reviews, a cheaper tier elsewhere). You are not married to one model or one company's roadmap.
  4. Structured cross-service provenance. Others can act on external services via MCP connectors; the hub + progress layer instead aggregate and attribute them — issues, PRs, CI, reviews (and, on the roadmap, Slack/email, data and support activity) — into a colourised, per-conversation ledger. The competitors stay largely git/PR-centric; decknix models the whole footprint of a piece of work.

Where it falls short — and what to borrow

The honest weak spots, and where a competitor already has the better idea:

  • Emacs-native is a floor and a ceiling. It's the point for Emacs users, but real onboarding friction for everyone else — the IDE/terminal tools have a gentler on-ramp. Nothing to borrow here; just a cost to own.
  • No one-command fan-out. "Spawn N agents, each in its own worktree, then diff and pick the winner" is the home turf of Cursor, Codex, Conductor, supacode and Claude Code's agent view. decknix is worktree-aware but doesn't yet fan out. Borrow: the worktree-per-agent launch plus a side-by-side diff/pick flow.
  • Sub-agent status is invisible. Sub-agents are discovered but carry no lifecycle state. Team feedback from trialling cmux specifically praised its per-session "needs input vs still running" indicator and auto-rename by conversation progress. Borrow both: colourise sub-agents by state (the progress layer's red/amber/green already models it) and generate a one-line progress summary (à la Claude Code's agent-view summaries) to auto-name sessions. This is Feature 1 of the resourcing roadmap.
  • No in-editor verification surface. cmux's embedded browser for PR review and rendered docs is genuinely handy. decknix already ships xwidget-webkit and the /verify skill — borrow the idea of surfacing them as a first-class review/verify affordance beside the session.
  • Sub-agents can't talk to each other. Claude Code's experimental agent teams give teammates a mailbox and a shared task list; decknix's sub-agents are report-only. Borrow inter-agent coordination if multi-agent workflows deepen.
  • Sessions aren't shareable. Amp's referenceable, team-visible Threads (@T-id) are a nice collaboration primitive; decknix sessions are local — worth considering for team review visibility.

Gaps decknix is positioned to bridge

Where the whole field has white space — and decknix already holds the primitives or a head start:

  • Multi-human, multi-agent governed pairing. Every tool here is single-human-with-agents. None let two or more people, each with their own agents, share one governed conversation — with full transcripts, durable artifacts, and an explicit path from "we discussed it" to "there is a PR in the right repo." decknix already sketches this direction in its draft Pair Protocol, and the pieces it would build on — the coordination substrate, per-conversation identity, and provenance primitives — are already in place. It is a genuinely open space: nobody in this landscape is building collaboration through agents, only operation of them.
  • A cross-service resourcing ledger + timeline. The C-c s a transient — a tree of a session and its sub-agents, each with what it produced: PRs authored/reviewed, linked issues across Jira and GitHub, worktrees, plus messaging, data, and support activity — over a swimlane timeline of when it happened. The hub, the progress layer, and per-conversation links are the raw material; no competitor aggregates provenance this way.
  • Accountability, not just fan-out. The field optimises starting many agents; decknix is positioned to own accounting for what they did — the provenance, attention, and audit trail of a piece of work across every service it touched.

The near-term build order (sub-agent state + colourisation → C-c s a resourcing transient → timeline) is tracked under Sidebar Layouts.

The wider landscape

The matrix samples the field; the fuller taxonomy shows where these tools cluster — and where the Agent Shell sits apart:

  • Terminal multiplexerscmux, supacode: panes + worktrees + attention rings, macOS-native, model-agnostic. Excellent at fan-out; no cross-service provenance. (cmux is GPL-3.0; supacode is FSL source-available, not classic OSS.)
  • Desktop orchestrators over agent CLIsConductor: free (bring your own subscription), worktree-per-agent, built-in diff/PR flow, start-from-Linear-issue.
  • Kanban-as-orchestrationVibe Kanban: a board that spawns any of ~10 agent CLIs into worktrees; Apache-2.0 — but now community-maintained after Bloop's April 2026 sunset, so weigh its longevity.
  • AI IDEsCursor: parallel agents + worktrees inside its own editor, plus cloud agents and Slack/Linear/GitHub triggers.
  • Multi-surface first-party agentsOpenAI Codex, Sourcegraph Amp: one agent across CLI/IDE/web/cloud; Amp adds native sub-agents and team-shared "Threads." Curated models; Amp has no worktree isolation.
  • Enterprise orchestratorsAugment Intent: coordinator → implementors → verifier over bring-your-own agents, worktree-backed.

Across all of them, the Agent Shell is the only one that is simultaneously editor-native (Emacs), declared as Nix config-as-code, and backed by a structured cross-service hub. The others optimise fan-out and PR flow; decknix optimises the coordination, provenance, and accountability of the work — and does it inside the editor you already live in.


Sources: cmux · supacode · Cursor · OpenAI Codex · Augment Intent · Claude Code · Conductor · Vibe Kanban · Sourcegraph Amp. Compiled July 2026; agent tooling moves fast — verify against each tool's current docs.

Sidebar Layouts

The Agents workspace sidebar (C-c A w) stacks four major sections — Requests → WIP → Live → Sessions — above a Keys/Toggles footer. Each section has its own row format, glyph set, and (for Requests) a multi-layout cycle.

These pages mock up the toggle states in colour, because colour is load-bearing in this UI: it encodes CI state, review state, age, and attention. A plain-text mock-up (the retired sidebar-demo.txt) could not carry that, so the layouts now live here.

One page per section:

  • Global — org filter, width, footer toggle states
  • Requests — the A/B/C/D layout cycle + column anatomy
  • WIP — grouping modes today plus a proposed columnar layout
  • Live — view modes + linked-PR / linked-repo rows

How to read these mock-ups

Mock-ups use a custom "sidebar DSL" inside <pre class="sb-markup"> blocks. This allows simple tags like {g}●{/} to render with the real foreground colours from the Emacs faces.

The colours are faithful to agent-shell/hub/decknix-hub-icons.el and agent-shell/hub-bulk/decknix-agent-shell-hub.el (cited per page). They are not screenshots — spacing is approximate; column order and colour are the contract.

Colour legend (source of truth)

SwatchHexSemanticWhere it appears
██#98c379success / greenapproved , merged , CI pass , approval , live worktree ⎇*, resolved comments
██#e06c75error / redchanges-requested /, CI fail, conflict /, age ≥3d
██#e5c07bwarning / yellowdraft , CI running, review-required, bot-pending b, needs-reply, age <3d, ?
██#61afefinfo / blueopen state word, team @, idle worktree , branch names
██#87d7afsoft greenhuman reply , bot reply 👽, reply-state comments column
██#87d7ffbright cyanactive-review indicator
██#d7af5fgoldme @, active-review row tint, needs-reply 💬
██#af5f87pink / mauvebot author π, bot-pending 🤖
██#5c6370dim greyno local clone , stale
██comment facedim defaultclosed , sha7, sub-day ages, (none) placeholders

Shape-family glyphs

The primary status icon folds PR state + CI + review into one glyph (decknix--hub-primary-status-icon, decknix-hub-icons.el:83):

GlyphMeaningColour rule
placeholder / pre-PR / local branchshadow
draftCI: pass / running / fail / orange soft-fail
open / in-reviewblocked / running / cyan commented / else shadow
open & approvedsuccess
merge conflicterror
/ merged / closedmerged green, closed dim
πbot author (overrides all above)pink

Worktree row badge

A fixed 2-column slot at the start of hub rows (decknix--hub-worktree-row-badge, hub.el:1672):

BadgeMeaning
⎇*branch is checked out in a worktree that is a live session
⎇ separate worktree of the local clone, no live session yet
↓ no local clone of the repo on this machine
··(two spaces) primary HEAD / branch ref only / no (repo, branch) context

Symbol style (y toggle)

y swaps the row-level activity icons (🤖 👽 💬 ) between emoji and an ASCII fallback (β/β/i/i). The Live section count badges (📥 📤) are always emoji and are not affected by the toggle. The shape-family glyphs and the columnar ⟳ ✓ ✗ always stay ASCII — Emacs faces cannot tint colour-emoji, so the colour contract above depends on them being plain glyphs.

Global

Global toggles affect the whole sidebar rather than one section. They live in the Global group of the T transient and are advertised in the footer. See the colour legend for the palette.

KeyToggleStates
OOrg filter[all] ↔ each enabled org (e.g. [upside])
WWidth[narrow][med][wide]
KKeys/Toggles footershow ↔ hide Navigate/Quick + Toggles lines

Org filter (O)

Hides every row whose repo is outside the selected org. The section counts update to match. Default is [all].

{hd}Requests (12){/}            ← all orgs
{hd}WIP (4){/}

{hd}Requests (7){/}             ← O → upside  (reapit/* hidden)
{hd}WIP (2){/}

Width (W)

Width drives truncation and the footer's layout. At narrow/med the footer toggle groups stack vertically; at wide (≥48 cols) Global+Requests and Live+WIP render side-by-side so the footer doesn't push content off-screen.

[med]                          [wide  ≥48 cols]
 Toggles                        Toggles
   Global: org [all] w [med]      Global: org [all] w [wide]   Requests: @ off …
   Requests: @ off ci [all] …     Live:   disp [A] view [flat]  WIP: linked [hide] …
   Live: disp [A] view [flat]
   WIP: linked [hide] stale [on]

The footer has two parts: the Navigate/Quick key hints and the Toggles state lines. K hides both so the section content gets the full height; the T transient still opens and changes them while hidden.

[KEYS SHOWN]                   [KEYS HIDDEN  (K)]
 Navigate  s sessions  r req…   {hd}Requests (12){/}
 Quick     c new  k kill        …section content only…
 Toggles
   Global: org [all] w [med]
   Requests: @ off ci [all] …

Note: the Toggles state line reflects the current value of every toggle by label only (no keys) — press T for the interactive transient where the keys are shown.

Source

  • Group definitions: agent-shell/sidebar/decknix-sidebar-toggles.el
  • Footer rendering: agent-shell/sidebar/decknix-sidebar-footer-keys.el

Requests

PR reviews assigned to me, oldest-first by default. This is the only section with a layout cycleD in the T transient steps A → B → C → D. All four render the same four example PRs below so you can compare:

  1. upside#16570 — 2d, open, CI pass, approved, I'm @-mentioned, and a live review session is already running (gold tint + ).
  2. upside#16568 — 15d, draft, bot-authored, no local clone.
  3. reapit#123 — 4h, open, CI failing / changes-requested, team requested.
  4. upside#16571 — 1d, open, CI running, needs my reply.

See the colour legend and shape-family for glyph/colour meanings.

Layout A — Full (default)

wt-badge · age₃ · repo#N · [icon @ activity ◉] · title. The primary icon folds CI+review into one shape (hub.el:2699).

{hd}Requests (4){/} ⇅
{c}⎇ {/} {y}2d{/} upside{gd}#16570{/} {g}●{/}{gd bd}@{/}{ac}◉{/} {gd}CSE-201: refactor token cache{/}
{dm bd}↓ {/}{r}15d{/} upside#16568 {bo}π{/}   {dm}bump: dependency update{/}
{dm bd}↓ {/} {dm}4h{/} reapit#123 {r}◐{/}{c bd}@{/}  CSE-204: fix pubsub timeout
    {y}1d{/} upside#16571 {y}◐{/}{gd}💬 {/} CSE-205: retry policy for webhook

The row for PR 1 is gold-tinted end to end because a live session is reviewing it; per-column colours (icon green, @ gold, age yellow) still show through (add-face-text-property … append, hub.el:2711).

Layout B — Scoped

icon · @activity · title (hub.el:2694). Drops age / repo / number — the phase-aware minimal signal. is gone but the active row keeps its gold tint.

{hd}Requests (4){/}
{g}●{/} {gd bd}@{/} {gd}CSE-201: refactor token cache{/}
{bo}π{/}   {dm}bump: dependency update{/}
{r}◐{/} {c bd}@{/}  CSE-204: fix pubsub timeout
{y}◐{/} {gd}💬 {/} CSE-205: retry policy for webhook

Layout C — Label

icon · state-label₁₆ · title (hub.el:2684). The label is the human-readable state (dim, fixed 16-col) from decknix--hub-format-row-label.

{hd}Requests (4){/}
{g}●{/} {dm}approved       {/} {gd}CSE-201: refactor token cache{/}
{bo}π{/} {dm}draft          {/} {dm}bump: dependency update{/}
{r}◐{/} {dm}CI failing     {/} CSE-204: fix pubsub timeout
{y}◐{/} {dm}awaiting review{/} CSE-205: retry policy for webhook

Layout D — Minimal

wt-badge · age₄ · detail · icon · repo#N · title (hub.el:2672). High-signal, compact; the phase is implied by the glyph. This is what the retired sidebar-demo.txt §5 showed.

{hd}Requests (4){/}
{c}⎇ {/}  {y}2d{/} {gd bd}@{/} {g}●{/} upside{gd}#16570{/} {gd}CSE-201: refactor token cache{/}
{dm bd}↓ {/} {r}15d{/}   {bo}π{/} upside#16568 {dm}bump: dependency update{/}
{dm bd}↓ {/}  {dm}4h{/} {c bd}@{/}   {r}◐{/} reapit#123 CSE-204: fix pubsub timeout
     {y}1d{/} {gd} 💬 {/} {y}◐{/} upside#16571 CSE-205: retry policy for webhook

Section-header badges

The Requests (N) header grows badges for active filters (hub.el:2581):

{hd}Requests (4){/} {gd bd}@{/}          mention filter = me        (M-… / @)
{hd}Requests (4){/} {c bd}@{/}          mention filter = team
{hd}Requests (4){/} {bo}🤖{/}         bot-authors = show
{hd}Requests (4){/} {bo}🤖{/}{gd bd}@{/}        bot-authors = mentioned-only
{hd}Requests (4){/} ⇅          sort reversed (newest-first)

Toggles (the T → Requests group)

Each toggle owns one signal — no toggle does another's job, so they compose freely to fine-tune the list. The label beside each key in the transient is spelled out (not icon-only) so the state is unambiguous.

KeyToggleEffect
DLayoutcycle A → B → C → D
@Mentionoff → me → team → me+team
FAgeall / 1d / 3d / 7d / 14d / 30d
CCIfilter by CI state
Bbot-authorshide → show → mentioned
b🤖 bot-pendinghide PRs whose latest activity is a bot (default on)
c💬 needs-my-replyhide PRs whose latest non-bot activity is someone else, i.e. awaiting my reply (default off)
o⏳ waiting-othershide PRs where I posted last and am waiting on others (default on)
M↩ replied-to-meonly PRs where a human replied in a thread I took part in
Rreviewedcycle showhide-minehide-any (default hide-any) — see below
ssort ⇅flip oldest↔newest (seeds the r picker)
X⚠ conflicthide mergeable = CONFLICTING PRs (default on)
x📝 drafthide draft PRs (default on)

The reviewed filter (R)

Cycles three states so you can focus on PRs that still need a review from someone:

  • show — every request, regardless of review status.
  • hide-mine — hide PRs I have already reviewed or commented on (my_review ∈ APPROVED / CHANGES_REQUESTED / COMMENTED). PRs only a colleague has touched stay visible.
  • hide-any (default) — also hide PRs a colleague has engaged with: another human's standing review (APPROVED / CHANGES_REQUESTED / COMMENTED) or a conclusive aggregate review_decision. Someone else is already on it.

In hide-mine and hide-any a PR resurfaces when it genuinely wants you again:

  • a real re-request (you reviewed and the author re-requested you),
  • a direct @-mention by name in a comment or review, or
  • the review has gone stale — the author moved the PR forward since it was reviewed (a commit landed after it, or changes were requested and every inline thread is now resolved).

These signals (re_requested, comment_mentioned, others_reviewed, review_stale) are computed by the hub daemon and read from github-reviews.json; being a merely still-requested reviewer no longer forces an already-approved PR to stay visible.

Reviewing from the picker (r)

r opens the Requests picker (consult). Select a row to act on it, or — because the picker no longer requires a match — paste a PR URL that isn't in the list and press RET to review it directly. The review launches through the same path as the C-c A c r quick action, so it inherits the pr-review purpose's provider / model / auto mode.

Source

  • Renderer + layout cycle: agent-shell/hub-bulk/decknix-agent-shell-hub.el:2563
  • Primary icon / age / activity icons: agent-shell/hub/decknix-hub-icons.el
  • Active-review tint: decknix--hub-request-tint-active, hub.el:2711

WIP

My own open PRs, grouped by repo. See the colour legend and shape-family.

Two things are orthogonal here:

  • Grouping (how rows nest) — shipped today, three modes.
  • Row layout (what each row shows) — today a single compact format; proposed below is a layout cycle borrowed from Requests.

Grouping modes (today)

Row format today: wt-badge · icon · age · kind · #N · branch.

Grouping modes — today
Row: wt-badge  icon  age  kind  #N  branch

Repo-only (default)
WIP (3)
  decknix
    ⎇*  15d pr    #123 feature/foo
    ↓  π  2d draft #130 dependabot/npm/lodash
  decknix-config
    ⎇    1h wip   feature/bar

Workspace → repo
WIP (4)
 ~/tools/decknix
    ⎇*  15d pr    #123 feature/foo
    ⎇    1h wip   feature/bar
 ~/Code/foobar
    ↓  π  2d draft #130 dependabot/npm/lodash

Workspace → repo → worktree
WIP (2)
 decknix
   ↓  π  2d draft #130 dependabot/npm/lodash
 foobar
   monolith
     ↓  π  2d draft #130 dependabot/npm/lodash

The last row in "Repo-only" is a worktree placeholder — a (repo, branch) in the worktree registry with no matching open PR yet (⎇ … wip), so freshly-created worktrees appear before gh pr create indexes (hub.el:2722).

Proposed columnar layout (draft)

Status: proposal for review. Not implemented. Borrows the Requests layout cycle (D) and the linked-PR signal zone so WIP carries the same scannable pipeline columns as Live's linked-PR rows.

Grouping stays as above; what changes is the row. A new D cycle:

Proposed A — Full (columnar)

wt · #N · age₃ · state₆ · CI · b · c · ✓ · [⚠] · DTSP · branch (the linked-PR signal zone, reused verbatim).

Proposed columnar layout (draft)
Column order: wt  #N  age  state  CI  b  c  ✓  [⚠]  DTSP  branch

Proposed A — Full
WIP (3)
  decknix
    ⎇* #123  2d open     b c       feature/foo
    ↓  #130  2d draft    b · ?    dependabot/npm/lodash
  decknix-config
    ⎇  #—    1h wip                   feature/bar

B — Scoped:    wt  icon  state  branch
C — Label:     wt  icon  state-label₁₆  branch
D — Minimal:   icon  age  kind  #N  branch  (today's default)

Proposed B/C/D

Mirror Requests so muscle memory transfers:

  • B — Scoped: wt · icon · state · branch (phase-aware, drops the signal zone).
  • C — Label: wt · icon · state-label₁₆ · branch (e.g. CI failing).
  • D — Minimal: today's compact row (icon · age · kind · #N · branch) — keeps the current default as the floor of the cycle.

Open questions for review

  • Does the signal zone belong on my own PRs, or is b/c/ noise when I'm the author? (Requests uses it for PRs I review.)
  • Placeholder rows have no PR — should they dim the whole signal zone (shown) or omit it and left-align the branch?
  • Should D (layout) and the grouping toggle share one key or stay separate?

Toggles (the T → WIP group, today)

KeyToggleEffect
Lhide linkedhide PRs already live as sessions
mstalehide MERGED/CLOSED (default on); off shows stale rows
Ppipelinedeploy (DTSP) indicators
r↩ replies-to-meparallel to Requests, own state
n💬 comments
u🤖 bot-review

Source

  • WIP renderer: agent-shell/hub-bulk/decknix-agent-shell-hub.el
  • Placeholder rows: decknix--hub-wip-placeholder-rows, hub.el:2722
  • Signal-zone formatter (to be reused): decknix--hub-pr-format-line, hub.el:2003

Live

Live agent sessions. Five view modes (cycle with v / the Live view toggle), plus optional linked-PR and linked-repo rows expanded beneath each session. See the colour legend.

Session status family

The leading marker uses the lifecycle shape-family (header-line faces): shape = stage, colour = state.

{dm}○{/} initializing   {y}◐{/} working   {r}◐{/} waiting (needs input)
{g}●{/} ready          {ac}●{/} finished  {r}●{/} killed

Provider glyph

Every live-session row is prefixed with a provider glyph — a single letter that identifies the AI backend running in that buffer:

GlyphProvider
AAuggie (Augment Code)
CClaude Code (Anthropic)
PPi
?Unknown / unregistered provider

The glyph is coloured with the same status face as the lifecycle marker so it reads as part of the same signal group. Additional providers registered via decknix-agent-register-provider automatically appear here using their :glyph field.

View modes

A session row is sel · glyph · marker · name · tile · [N⬆ N✓] · 📥/📤/↩/👽 · progress. > marks the selected buffer. Tags are capped at 3 for readability; full tags are always visible in the header-line of the buffer itself.

Flat

{hd}Live (3){/}
 > {y}A{/} {y}◐{/} feature/foo  {rp}decknix{/}        [2⬆ 1✓] {r bd}📥1{/}
   {g}A{/} {g}●{/} feature/bar  {rp}decknix-config{/}
   {g}C{/} {g}●{/} review/auth  {rp}decknix{/}        [1⬆ 0✓] {sg bd}↩{/}

Grouped by workspace

{hd}Live (3){/}
 {dm}~/tools/decknix{/}
   > {y}A{/} {y}◐{/} feature/foo   [2⬆ 1✓] {r bd}📥1{/}
     {g}C{/} {g}●{/} review/auth
 {dm}~/Code/nurturecloud/decknix-config{/}
     {g}A{/} {g}●{/} feature/bar

Grouped by path (last component, tag stripped)

{hd}Live (3){/}
 {rp}decknix{/}
   > {y}A{/} {y}◐{/} feature/foo   [2⬆ 1✓] {r bd}📥1{/}
     {g}C{/} {g}●{/} review/auth
 {rp}decknix-config{/}
     {g}A{/} {g}●{/} feature/bar

Grouped by shared tags

{hd}Live (5){/}
 {dm}nurturecloud/CONN{/}
   > {y}A{/} {y}◐{/} #10861/ARC
     {g}A{/} {g}●{/} #7/#202
 {dm}Other{/}
     {g}C{/} {g}●{/} review/auth
     {r}A{/} {r}◐{/} nurturecloud
     {g}A{/} {g}●{/} decknix-config

Grouped by first tag (tree)

{hd}Live (5){/}
 {dm}nurturecloud{/}
   > {y}A{/} {y}◐{/} CONN/#10861
     {g}A{/} {g}●{/} CONN/#7
 {dm}review{/}
     {g}C{/} {g}●{/} auth
 {dm}Other{/}
     {g}A{/} {g}●{/} decknix-config

Attention badges (hub enabled): {r bd}📥N{/} linked PRs awaiting my action, {g bd}📤N{/} ones I've acted on, {sg bd}↩/👽{/} when any linked PR has replies to me. Terminal (MERGED/CLOSED) PRs are excluded so stale links don't add noise.

Sub-agent rows

When a session has spawned sub-agents (Claude Code sub-agents are stored in a subagents/ directory next to the session transcript), they are shown as child rows beneath the parent, indented by 4 characters and dimmed:

   {g}C{/} {g}●{/} review/auth
     {dm}↳ C claude-3-5-sonnet{/}
     {dm}↳ C computer-use{/}

The provider glyph on sub-agent rows always matches the parent session's backend. The name is the sub-agent's slug (model identifier or tool name).

Linked-PR rows

Shown under a session when the PRs toggle (E) is on. Fixed-width columns so pipeline progress stays scannable across expand modes (decknix--hub-pr-format-line, hub.el:2003):

#N · age₃ · state₆ · CI · b · c · ✓ · [⚠] · DTSP

   {rp}decknix{/}
     #123  {dm}2d{/} {c bd}open  {/}  {g}⟳{/} {dm}b{/} {sg bd}c{/} {g bd}✓{/}           feature/foo
     #118  {dm}5d{/} {g}merged{/}  {g}⟳{/} {dm}·{/} {dm}·{/} {dm}·{/}           hotfix/login
     {dm}⊳{/} #99 {dm}1d{/} {y bd}draft {/}  {r}⟳{/} {y bd}b{/} {dm}·{/} {y bd}?{/} {r}⚠{/}        fork/patch
  • state (6-wide, left-pad): {c}open{/} / {y}draft{/} / {g}merged{/} / {dm}closed{/}.
  • CI : {g}pass{/} / {y}running{/} / {r}fail{/} / {dm}idle{/} — always shown.
  • b bot, c comments, approval — see the legend. Merged rows collapse review columns to dim ·; closed rows stop at the state word.
  • appears only on OPEN conflicting rows (mergeable = CONFLICTING).
  • prefixes a subject PR (I was added as reviewer).

Linked-repo rows

For repos worked by pushing directly to a branch (linked via C-c A c L). Intermixed with PR rows under the same repo header (decknix--hub-repo-format-line, hub.el:2208):

branch · sha7 · age₃ · CI · DTSP

   {rp}decknix-config{/}
     {br}main{/}      {dm}a1b2c3d{/} {dm}3h{/}  {g}⟳{/}
     {br}staging{/}   {dm}9f8e7d6{/} {dm}1d{/}  {r}⟳{/}

Repo rows intentionally have no state/bot/comment/approval columns — there's no PR to review.

Toggles (the T → Live group)

KeyToggleEffect
vview modeflat → workspace → path → tags → tree → flat
ddisplay mode (linked PRs)off / PR / pipeline / both
Hhiddenshow/hide hidden sessions
Nrepo-name capshort / medium / full
EPRsoff / PR / pipeline / both
ysymbol styleascii ↔ emoji
ttileoff → 2 → 3 → 4 → off

Source

  • Live renderer: agent-shell/workspace-bulk/decknix-agent-shell-workspace.el
  • Linked PR / repo formatters: agent-shell/hub-bulk/decknix-agent-shell-hub.el

Vision: The Future of AI Tooling in Decknix

The current agent shell is Layer 5 of a broader vision. Here's where it's heading.

Literate Session Export (Next)

Issue: decknix#55

Every AI session becomes publishable knowledge:

C-c E o    → Export to Org-mode
C-c E m    → Export to Markdown
C-c E h    → Export to HTML
C-c E c    → Export to Confluence (ADF)

Why this matters: An investigation session becomes a post-mortem. An architecture discussion becomes a design doc. A bug hunt becomes a runbook. The AI conversation is the documentation — export makes it shareable.

Role-Based Workflow Profiles

The agent shell currently serves a single persona: the developer writing code. But AI-assisted workflows extend far beyond coding.

Engineering Workflows

WorkflowWhat the Agent Does
InvestigationQuery logs (GCP MCP), search knowledge base, correlate errors, produce root cause analysis
Architecture ReviewAnalyse codebase structure, identify coupling, suggest decomposition, generate ADRs
Incident ResponseReal-time log tailing, alert correlation, runbook execution, post-mortem drafting
Code ReviewAutomated review on commit, PR summary generation, review thread resolution
OnboardingGuided codebase exploration, convention explanation, first-task scaffolding

Transformative Engineering

Beyond individual developer productivity, the tooling enables transformative engineering — systematic, AI-assisted modernisation of large codebases:

CapabilityDescription
Migration planningAnalyse a legacy codebase, identify migration paths, estimate effort, generate step-by-step plans
Pattern extractionDetect repeated patterns across services, propose shared libraries, generate extraction PRs
Observability gap analysisCompare metric/alert coverage against error hierarchies, identify blind spots
Test coverage expansionAnalyse untested paths, generate test scaffolds, prioritise by risk
Cross-service coherenceValidate that API contracts, event schemas, and alert definitions stay consistent across services

Beyond Engineering

The same session-as-first-class-object model applies to non-engineering roles:

RoleWorkflow
ProductSpec refinement sessions, user story generation, acceptance criteria drafting
QATest plan generation, exploratory testing guidance, regression analysis
SupportTicket investigation with knowledge base search, escalation drafting
LeadershipSprint retrospective analysis, technical debt quantification, roadmap impact assessment

Workflow Templates

Future slash commands and templates will be role-aware:

/investigate <property-id>     → Full NC property sync investigation
/incident <alert-name>         → Incident response runbook
/review-pr <PR-number>         → Structured code review
/onboard <repo-name>           → Guided codebase tour
/migrate <from> <to>           → Migration planning session

These would combine MCP server access, knowledge base search, and structured output into repeatable workflows.

Multi-Agent Orchestration

The session manager and attention tracker already support multiple concurrent sessions. The next step is coordinated multi-agent workflows:

  • Parallel investigation — spawn multiple agents to investigate different aspects of an incident simultaneously
  • Review pipeline — one agent reviews code, another checks test coverage, a third validates observability
  • Continuous monitoring — background agents that watch CI, alert channels, or deployment status and inject findings into active sessions

Declarative Workflow Definitions

Workflows as Nix configuration:

{ ... }: {
  decknix.ai.workflows = {
    investigate = {
      description = "Full property sync investigation";
      mcpServers = [ "gcp-monitoring" "org-knowledge-base" ];
      template = "investigate";
      context.autoPin = [ "jira" ];  # Auto-pin Jira tickets mentioned
    };
    incident = {
      description = "Incident response runbook";
      mcpServers = [ "gcp-monitoring" "pagerduty" ];
      template = "incident";
      attention.priority = "high";  # Always show in attention tracker
    };
  };
}

The Endgame

The vision is an environment where:

  1. Every AI conversation produces artefacts — not just code changes, but documentation, decisions, and knowledge
  2. Workflows are reproducible — a new team member gets the same investigation tools, templates, and MCP access as a senior engineer
  3. Context is continuous — switching between sessions preserves the full picture of what you're working on
  4. The tooling adapts to the role — engineers, product managers, and support staff each get workflows tailored to their needs
  5. The environment is declarativedecknix switch reproduces the entire AI-assisted workflow on any machine

decknix CLI

The decknix CLI is a Rust binary that provides the primary interface for managing your configuration.

Usage

decknix [COMMAND]

Commands:
  switch    Switch system configuration
  update    Update flake inputs
  help      Show help (including extensions)
  <ext>     Run a user-defined extension

The CLI automatically discovers user extensions defined via Nix and displays them alongside built-in commands.

Architecture

┌──────────────┐     ┌──────────────────────────┐
│  Rust Binary  │ ──→ │  darwin-rebuild / nix     │
│  (decknix)    │     │  (actual build commands)  │
└──────┬───────┘     └──────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────┐
│  Extension Discovery                          │
│  /etc/decknix/extensions.json  (system)       │
│  ~/.config/decknix/extensions.json  (home)    │
└──────────────────────────────────────────────┘

The binary always runs from ~/.config/decknix/ so --flake .#default resolves correctly.

Getting Help

# Show all commands (built-in + extensions)
decknix help

# Help for a specific command
decknix switch --help
decknix help board

Next

Core Commands

decknix switch

Build and activate your configuration.

Usage: decknix switch [OPTIONS]

Options:
    --dry-run                    Build only — don't activate
    --force                      Bypass the preflight equality check and always activate
    --override <INPUT=PATH>      Override a flake input with a local path (repeatable)
    --no-overrides               Ignore [switch.overrides] in settings.toml

Examples

# Normal switch (skips sudo activation if nothing changed)
decknix switch

# Dry run (check for errors without activating)
decknix switch --dry-run

# Force re-activation even when the built system matches the current one
decknix switch --force

# Test a local framework checkout
decknix switch --override decknix=~/tools/decknix

# Override multiple inputs (repeat the flag)
decknix switch --override decknix=~/tools/decknix --override nc-config=~/Code/my-org/decknix-config

How It Works

  1. cd ~/.config/decknix
  2. Preflight (unless --dry-run or --force): evaluates the system derivation via nix build --no-link --print-out-paths and compares the resulting store path with readlink /run/current-system.
    • Match → skips sudo darwin-rebuild switch entirely, verifies user LaunchAgents (org.nixos.*) are running, kickstarts any that are down, and exits.
    • Differ → prints old/new store paths and proceeds with activation.
  3. Runs sudo darwin-rebuild switch --flake .#default --impure (reusing the cached preflight build).
  4. For each active override (CLI or settings.toml), adds --override-input <INPUT> path:<PATH>.
  5. With --dry-run, uses build instead of switch and skips the preflight.

Why the preflight

Once you've applied a configuration, re-running decknix switch with no code changes should be a fast no-op. The preflight lets Nix's evaluation cache do the work (typically 1–3s) instead of paying for a full sudo darwin-rebuild switch (30–90s of activation scripts). The --force flag is there for when you deliberately want to re-run activation — for example, after manually editing a launchd plist or when debugging an activation script.

Persistent overrides via settings.toml

If you routinely run decknix switch with the same --override flags (e.g. you keep local checkouts of the framework and your org config), you can pin them once in ~/.config/decknix/settings.toml:

[switch.overrides]
decknix = "~/tools/decknix"
nc-config = "~/Code/my-org/decknix-config"

Every decknix switch then applies those overrides by default. Precedence, from highest to lowest:

  1. --override INPUT=PATH on the command line (per-input; wins over config)
  2. [switch.overrides] in settings.toml
  3. The published flake inputs (from flake.lock)

The status line annotates each override with [config] when it came from settings.toml, so it's always clear where a given path was sourced from:

🔄 Switching (decknix=/Users/you/tools/decknix [config], nc-config=/Users/you/Code/foo/decknix-config [config])...

To force a switch against the published inputs (ignoring settings.toml entirely), pass --no-overrides:

# Ignore settings.toml — use whatever is pinned in flake.lock
decknix switch --no-overrides

# Ignore settings.toml but apply one one-off override
decknix switch --no-overrides --override decknix=~/experiments/decknix

settings.toml lives alongside your user config; it is a personal file and should not be checked into a shared decknix-config repo. If your decknix-config doesn't already ignore it, add it:

settings.toml

decknix update

Update flake inputs (dependencies).

Usage: decknix update [INPUT]

Arguments:
    [INPUT]  Specific input to update (optional)

Examples

# Update all inputs
decknix update

# Update only decknix
decknix update decknix

# Update only nixpkgs
decknix update nixpkgs

Runs nix flake update [input] under the hood. After updating, run decknix switch to apply.

decknix session

Find, create, resume, and tag agent sessions from the terminal — the workspace- and tag-aware CLI companion to the in-editor session tooling. It resolves against the same session store the Emacs sidebar and pickers use (Claude and Auggie), so a session you spawn in Emacs can be resumed here and vice versa, and tags are shared both ways.

Usage: decknix session <COMMAND>

Commands:
  list    List sessions in a workspace (default: current directory)
  resume  Resume a session (exec into the agent by default)
  new     Start a new session (exec into the agent by default)
  tag     Add or remove tags on a session's conversation
  tags    List all known tags with usage counts

decknix session list

List sessions (newest first), scoped to a workspace by default.

Options:
      --agent <AGENT>          claude, auggie, or all [default: all]
      --workspace <WORKSPACE>  Workspace to list (default: current directory)
      --all                    List across every workspace instead of just one
      --tag <TAGS>             Only sessions carrying this tag (repeatable; all must match)
      --grep <GREP>            Only sessions whose transcript matches this regex
      --since <SINCE>          Only sessions touched within a window (e.g. 7d, 12h, 30m)
      --limit <LIMIT>          Cap the number of rows
      --json                   Emit JSON instead of aligned columns
# Sessions in this workspace, all agents
decknix session list

# Claude sessions tagged #dos touched in the last day, as JSON
decknix session list --agent claude --tag dos --since 1d --json

# Everything across every workspace whose transcript matches a regex
decknix session list --all --grep "replay.dlq"

decknix session resume

Resume a session — by id/prefix, by tag (the latest match), or the most recent in scope. Execs into the agent by default; -n/--print prints the resolved command instead of running it.

Usage: decknix session resume [OPTIONS] [ID]

Arguments:
  [ID]  Session id or unique prefix

Options:
      --agent <AGENT>          claude, auggie, or all [default: all]
      --tag <TAGS>             Resume the latest session carrying this tag (repeatable)
      --last                   Resume the most recently touched session in scope
      --workspace <WORKSPACE>  Workspace to resolve within (default: current directory)
      --all                    Resolve across every workspace
  -n, --print                  Print the resolved command instead of exec-ing it
# Resume by id prefix
decknix session resume d8df9eb9

# Resume the latest session tagged #dos in this workspace
decknix session resume --tag dos

# Resume the most recent session anywhere — just print the command
decknix session resume --last --all --print

decknix session new

Start a new session, optionally with an initial prompt (everything after --) and pre-applied tags.

Usage: decknix session new [OPTIONS] [-- <PROMPT>...]

Arguments:
  [PROMPT]...  Initial prompt (everything after `--`)

Options:
      --agent <AGENT>          claude or auggie [default: claude]
      --tag <TAGS>             Pre-tag the conversation (requires an initial prompt to key it)
      --workspace <WORKSPACE>  Workspace to start in (default: current directory)
      --model <MODEL>          Per-conversation model override
  -n, --print                  Print the resolved command instead of exec-ing it
# New Claude session in this workspace
decknix session new

# New session pre-tagged and seeded with a prompt
decknix session new --tag dos --tag triage -- "Investigate ALR-5752"

decknix session tag / decknix session tags

Add or remove tags on a conversation, or list every known tag with usage counts.

# Tag / untag a conversation (by id or unique prefix)
decknix session tag d8df9eb9 --add dos --add day5
decknix session tag d8df9eb9 --remove day5

# List all known tags with counts (--json for machine output)
decknix session tags
decknix session tags --json

Tags are shared with the Emacs session tooling (C-c A T), so a session tagged here appears under that tag in the sidebar and pickers — and the agents the DoS board spawns on a ticket can be found and resumed later by tag.

decknix wt

Manage git worktrees across all your repos from one place. decknix keeps a registry of every worktree it knows about (used by the Emacs sidebar too), and decknix wt inspects and cleans that set — handy when agent work spins up many short-lived worktrees.

Usage: decknix wt <COMMAND>

Commands:
  list                List all worktrees from the registry
  refresh             Re-probe worktrees and update the cache
  audit               Dry-run report: stale / dirty / orphan-fork / branch-deleted-upstream
  orphans             List worktrees whose upstream branch has been deleted
  clean               Clean up old merged worktrees
  prune               Expunge stale worktrees (directory + branch + metadata + fork-remotes)
  prune-metadata      Prune git worktree metadata only
  clean-fork-remotes  Sweep orphan fork remotes
  registry            Dump the registry

Most cleanup verbs are dry-run by default — they report what they would do and only act when you add --apply.

Common options (audit / clean / orphans / prune):
  -r, --regex <REGEX>            Match against repo identifier or worktree path
      --older-than <OLDER_THAN>  Only worktrees older than a window (e.g. 7d, 12h, 30m)
      --apply                    Actually perform the deletion (else dry-run)
      --json                     Machine-readable output (list / audit / orphans)
# See every worktree decknix tracks (optionally one repo)
decknix wt list
decknix wt list --repo UpsideRealty/pubsub-dlq-forwarder

# Dry-run health report: stale, dirty, orphaned, upstream-branch-deleted
decknix wt audit
decknix wt audit --json

# Worktrees whose upstream branch was deleted (e.g. after a merged PR)
decknix wt orphans

# Remove old, MERGED worktrees — preview, then apply
decknix wt clean --older-than 7d
decknix wt clean --older-than 7d --apply

# Full sweep of stale worktrees (dir + branch + metadata + fork remotes)
decknix wt prune --apply --safe-delete-branch

decknix help

Show help for all commands, including dynamically discovered extensions.

# Show all commands
decknix help

# Help for a specific command or extension
decknix help switch
decknix help board

Extensions show their description and underlying command.

Extensions

The decknix CLI supports user-defined subcommands via a Nix-based extension system.

How It Works

Extensions are defined in Nix and compiled into JSON config files that the Rust binary reads at runtime:

/etc/decknix/extensions.json          ← system-level (from programs.decknix-cli.subtasks)
~/.config/decknix/extensions.json     ← home-level (from decknix.cli.extensions)

Both files are merged. Extensions appear in decknix help and support --help.

Defining Extensions (Home-Manager)

{ ... }: {
  decknix.cli.extensions = {
    board = {
      description = "Issue dashboard across repos";
      command = "${boardScript}/bin/decknix-board";
    };
    cheatsheet = {
      description = "Show WM keybinding cheatsheet";
      command = "${cheatsheetScript}/bin/decknix-cheatsheet";
    };
  };
}

Defining Extensions (System-Level)

# system.nix
{ ... }: {
  programs.decknix-cli.subtasks = {
    cleanup = {
      description = "Garbage collect Nix store";
      command = "nix-collect-garbage -d";
      pinned = true;  # Also creates standalone 'cleanup' command
    };
  };
}

Setting pinned = true creates a standalone wrapper so you can run cleanup directly without the decknix prefix.

Built-in Extensions

Decknix ships with several extensions:

CommandDescription
decknix boardIssue dashboard across GitHub repos
decknix cheatsheetShow window manager keybinding cheatsheet
decknix spaceSpace picker (GUI)
decknix verifyVerify system integration

decknix board — cross-repo issue dashboard

Prints a compact, colourised dashboard of GitHub issues across your configured repos (open/closed counts per repo, then the open issues with their labels). It is a thin wrapper over gh issue list, so it accepts that command's flags and passes them through per repo:

# The board (open issues across all configured repos)
decknix board

# Filter by label / assignee / search, or show closed issues
decknix board --label enhancement
decknix board --assignee @me
decknix board --state closed --limit 20
decknix board --search "sidebar in:title"

Requires an authenticated gh (gh auth status). The repo set comes from the extension's own configuration.

Zsh Completion

Extensions automatically get zsh tab-completion. The module generates a completion script that includes all built-in commands plus discovered extensions.

Using Extensions

# Run an extension
decknix board

# Pass arguments
decknix board open --no-color

# Get help
decknix board --help
# Or:
decknix help board

Arguments after the extension name are passed through as $1, $2, etc.

Decknix Hub

Background work-item aggregator — surfaces PR reviews, WIP PRs, Jira tasks, and CI status in your Emacs sidebar without blocking the editor.

Overview

decknix-hub is a lightweight Rust daemon managed by launchd. It polls external services (GitHub, Jira, TeamCity) on independent timers and writes per-adapter JSON files to ~/.config/decknix/hub/. Emacs watches this directory with file-notify and refreshes the sidebar instantly when data changes — zero polling from Emacs, zero main-thread blocking.

┌──────────────────────────────────────────────────────┐
│              decknix-hub (launchd)                    │
│  ┌──────────┐  ┌──────────┐  ┌──────┐  ┌─────────┐ │
│  │  GitHub   │  │  GitHub  │  │ Jira │  │TeamCity │ │
│  │  Reviews  │  │   WIP    │  │ 120s │  │  60s    │ │
│  │  60s poll │  │  120s    │  │ poll │  │  poll   │ │
│  └─────┬────┘  └─────┬────┘  └──┬───┘  └────┬────┘ │
│        ▼              ▼          ▼            ▼      │
│  github-reviews  github-wip  jira-tasks  teamcity-  │
│      .json         .json       .json    builds.json  │
│        └──────────┬──────────────┘            │      │
│          ~/.config/decknix/hub/               │      │
└───────────────────┬───────────────────────────┘
                    │ file-notify
                    ▼
           Emacs sidebar refresh

Quick Start

1. Enable the daemon

In your decknix-config (e.g., ~/.config/decknix/configuration.nix):

decknix.services.hub.enable = true;

2. Apply the configuration

decknix switch

This starts a launchd user agent (com.decknix.hub) that runs in the background. The Emacs sidebar integration is enabled by default — once the daemon writes its first data, the Requests and WIP sections appear automatically.

3. Verify it's running

# Check the launchd agent
launchctl list | grep decknix-hub

# One-shot test (doesn't need launchd)
decknix-hub --once

# Check the data
ls ~/.config/decknix/hub/
cat ~/.config/decknix/hub/meta.json

Configuration Options

Darwin (daemon)

GitHub

OptionDefaultDescription
decknix.services.hub.enablefalseStart the launchd daemon
decknix.services.hub.github.enabletrueEnable GitHub adapter
decknix.services.hub.github.reviewsInterval60Seconds between review polls
decknix.services.hub.github.wipInterval120Seconds between WIP polls
decknix.services.hub.github.reviewRepos[]Repos to check (empty = all)

Jira

OptionDefaultDescription
decknix.services.hub.jira.enablefalseEnable Jira adapter
decknix.services.hub.jira.baseUrl""Jira base URL (e.g. https://myorg.atlassian.net)
decknix.services.hub.jira.email""User email for API auth (typically wired from config.<org>.user.email)
decknix.services.hub.jira.apiTokenFile~/.config/decknix/local/jira-tokenPath to Jira API token file
decknix.services.hub.jira.project""Jira project key (e.g. NC)
decknix.services.hub.jira.statuses["Ready" "In Progress" "Blocked" "Code Review"]Statuses to poll
decknix.services.hub.jira.interval120Seconds between polls
decknix.services.hub.jira.maxResults50Max tasks per poll

TeamCity

OptionDefaultDescription
decknix.services.hub.teamcity.enablefalseEnable TeamCity adapter
decknix.services.hub.teamcity.proxyUrlhttp://localhost:8080IAP proxy URL
decknix.services.hub.teamcity.interval60Seconds between polls
decknix.services.hub.teamcity.repos[]Repos to cross-link with WIP branches
decknix.services.hub.teamcity.recentFinishedCount1Recent finished builds per branch

Identity Wiring

Org configs typically wire identity from config.<org>.user.email (set via identity.nix):

# In org config system.nix:
decknix.services.hub.jira.email = lib.mkDefault config.nurturecloud.user.email;

See Config Loader — Identity Files for details.

Emacs (sidebar integration)

OptionDefaultDescription
programs.agent-shell.decknix.hub.enabletrueShow hub data in sidebar

Requests

PR reviews assigned to you, ordered oldest first. Each line shows:

 Requests (8)
  72d repo#142 ✓ Fix payment widget
   3d repo#219 ⟳ Refactor extract
  • Age — colour-coded: ≥3 days = red, <3 days = yellow
  • CI pass, fail, running
  • RET on a line opens the PR in the browser

WIP

Your open PRs grouped by repository, with TeamCity build status:

 WIP (3)
   my-repo
  5h #221  ✓ ✓ feat: new thing
  3d #219  ⟳ ⟳42% refactor: extract
   other-repo
  1d #37   ✗ ✗ fix: update deps

The first icon is GitHub CI, the second is TeamCity (when enabled). TeamCity running builds show progress percentage.

Tasks

Jira tasks assigned to you, grouped by status:

 Tasks (4)
  ● NC-1234 In Progress  Implement hub daemon
  ◐ NC-1235 Code Review  Fix payment widget
  ✕ NC-1236 Blocked      Waiting for API access
  ○ NC-1237 Ready        Update documentation

Status icons: In Progress, Code Review, Blocked, Ready. Press RET on a task to open it in Jira.

Org Filter (O)

Press O in the sidebar to cycle through GitHub owners/orgs: allldeckUpsideRealtyall. Filters both Requests and WIP.

Troubleshooting

Sidebar shows "not running — ? O for setup"

The daemon isn't running. Enable it:

decknix.services.hub.enable = true;

Then decknix switch.

Sidebar shows "waiting for data…"

The hub directory exists but no JSON files yet. The daemon may have just started. Check logs:

cat /tmp/decknix-hub.log

gh authentication issues

The daemon uses the gh CLI for GitHub access. Ensure you're authenticated:

gh auth status

Data Files

All state is stored in ~/.config/decknix/hub/:

FileContent
github-reviews.jsonPR reviews needing your attention (per-PR attention signals — my_review, others_reviewed, re_requested, comment_mentioned, review_stale, review_decision — drive the sidebar's reviewed filter)
github-wip.jsonYour open PRs with CI status and branches
jira-tasks.jsonJira tasks assigned to you
teamcity-builds.jsonTeamCity build status for WIP branches
meta.jsonAdapter health: last poll time, errors

Each adapter writes independently — a slow Jira poll won't block GitHub data from refreshing.

Future Adapters (Planned)

  • Slack — unread mentions requiring follow-up
  • macOS notifications — new reviews, CI failures

Adding Org Configs for Your Team

This guide walks through creating a shared configuration repo for your organisation.

Step 1: Create the Repo

mkdir my-org-config && cd my-org-config
git init

Step 2: Create the Flake

# flake.nix
{
  description = "My Org - Decknix Config";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self, nixpkgs, ... }: {
    darwinModules.default = import ./system.nix;
    homeModules.default = import ./home.nix;
  };
}

Step 3: Define Team Packages

# home.nix
{ pkgs, ... }: {
  home.packages = with pkgs; [
    awscli2
    terraform
    jdk17
    nodejs
    python3
  ];
}
# system.nix
{ pkgs, ... }: {
  homebrew.casks = [
    "docker"
    "slack"
  ];
}

Step 4: Push and Reference

git add . && git commit -m "Initial org config"
git remote add origin git@github.com:MyOrg/decknix-config.git
git push -u origin main

Team members add it to their ~/.config/decknix/flake.nix:

inputs.my-org = {
  url = "github:MyOrg/decknix-config";
  inputs.nixpkgs.follows = "nixpkgs";
};

And wire the modules:

outputs = inputs@{ decknix, ... }:
  decknix.lib.mkSystem {
    inherit inputs;
    settings = import ./settings.nix;
    darwinModules = [ inputs.my-org.darwinModules.default ];
    homeModules   = [ inputs.my-org.homeModules.default ];
  };

Step 5: Test Before Merging

Point decknix switch at your local org-config checkout with --override <input>=<path> — the input name is whatever you gave it in flake.nix (e.g. my-org):

decknix switch --override my-org=~/Code/my-org/decknix-config

Or manually, bypassing the CLI:

sudo darwin-rebuild switch --flake .#default --impure \
  --override-input my-org path:~/Code/my-org/decknix-config

If your team members will iterate on the org-config regularly, they can pin the local path once in ~/.config/decknix/settings.toml so plain decknix switch uses it automatically — see decknix switch → Persistent overrides.

Step 6: Automate Updates

Add Renovate or Dependabot to auto-PR when the org config updates.

Team members pull updates with:

decknix update my-org
decknix switch

Tips

  • Keep the org config minimal — only team-wide requirements
  • Let individuals override in ~/.config/decknix/<org-name>/
  • Add a bootstrap.sh for one-command onboarding
  • Include a secrets.nix.example showing what credentials team members need

Framework Development

This guide covers how to develop and test changes to the decknix framework itself.

Setup

Clone the framework:

git clone git@github.com:ldeck/decknix.git ~/tools/decknix

Testing Changes

Quick Test with --override

The fastest way to test framework changes is to point decknix switch at your local checkout:

# Edit framework code
$EDITOR ~/tools/decknix/modules/home/options/editors/emacs/default.nix

# Build using local checkout
decknix switch --override decknix=~/tools/decknix

--override INPUT=PATH is repeatable — override the framework and an org-config at the same time:

decknix switch \
  --override decknix=~/tools/decknix \
  --override nc-config=~/Code/my-org/decknix-config

Each --override becomes --override-input <INPUT> path:<PATH> on the underlying darwin-rebuild call.

Persist Overrides for Every Switch

If you always run with the same local checkouts, pin them once in ~/.config/decknix/settings.toml so plain decknix switch picks them up automatically:

[switch.overrides]
decknix   = "~/tools/decknix"
nc-config = "~/Code/my-org/decknix-config"

CLI --override flags win per-input; pass --no-overrides to ignore the config file for a single run (useful when reproducing an issue against the published inputs). See decknix switch → Persistent overrides for the full precedence rules.

Manual Override

If you'd rather bypass the CLI entirely:

cd ~/.config/decknix
sudo darwin-rebuild switch --flake .#default --impure \
  --override-input decknix path:~/tools/decknix

Project Structure

decknix/
├── cli/src/main.rs          # Rust CLI binary
├── lib/default.nix          # mkSystem + configLoader
├── modules/
│   ├── cli/                 # CLI nix-darwin module
│   ├── darwin/              # System modules
│   └── home/                # Home-manager modules
│       └── options/
│           ├── cli/         # auggie, board, extensions
│           ├── editors/     # emacs/, vim/
│           └── wm/          # aerospace/, hammerspoon/
├── pkgs/                    # Custom packages
└── flake.nix                # Framework flake

Adding a New Module

  1. Create the module file in the appropriate directory:
# modules/home/options/my-tool.nix
{ config, lib, pkgs, ... }:
let cfg = config.decknix.myTool;
in {
  options.decknix.myTool.enable = lib.mkEnableOption "My Tool";

  config = lib.mkIf cfg.enable {
    home.packages = [ pkgs.my-tool ];
  };
}
  1. The module is auto-imported — all .nix files in modules/home/options/ are loaded.

  2. Test: decknix switch --override decknix=~/tools/decknix

Verifying

# Check the generated Emacs config
find /nix/store -name "default.el" -path "*/emacs-packages-deps/*" 2>/dev/null | head -1 | xargs cat

# Check loaded modules
decknix switch --override decknix=~/tools/decknix 2>&1 | grep "\[Loader\]"

# Evaluate an option
nix repl
:lf .
darwinConfigurations.default.config.programs.emacs.decknix.languages.kotlin.enable

Troubleshooting

Emacs Daemon Issues

# Check if daemon is running
launchctl list | grep emacs

# Restart daemon
launchctl stop org.nix-community.home.emacs
launchctl start org.nix-community.home.emacs

# View logs
log show --predicate 'process == "emacs"' --last 1h

Keybindings Not Working

  1. Did you run decknix switch?
  2. Check for conflicting configs: ~/.emacs, ~/.emacs.d/init.el
  3. Test in Emacs: M-x describe-key RET <key>

Troubleshooting

Common Issues

"command not found: decknix"

The CLI hasn't been installed yet. Run the full command manually:

sudo darwin-rebuild switch --flake ~/.config/decknix#default --impure

After the first successful switch, decknix will be in your PATH.

Build Errors

Check the loader trace to see which files are being loaded:

decknix switch 2>&1 | grep "\[Loader\]"

Common causes:

  • Syntax error in a .nix file — check the error message for the file path
  • Missing input — run nix flake update to fetch all inputs
  • Stale lock file — delete flake.lock and rebuild

Config Not Taking Effect

  1. Did you run decknix switch?
  2. Check that your file is in the right location (the loader traces what it finds)
  3. Another module may be setting the value with higher priority — try lib.mkForce

Emacs Daemon Not Starting

# Check service status
launchctl list | grep emacs

# View logs
log show --predicate 'process == "emacs"' --last 1h

# Manually start
launchctl start org.nix-community.home.emacs

Emacs Keybindings Not Working

  1. Rebuild: decknix switch
  2. Restart Emacs: pkill emacs && launchctl start org.nix-community.home.emacs
  3. Check for conflicting configs:
    • ~/.emacs
    • ~/.emacs.d/init.el
    • ~/.config/emacs/init.el
  4. Test in Emacs: M-x describe-key RET then press the key

Emacsclient Can't Connect

# Check if daemon is running
ps aux | grep "emacs.*daemon"

# Try starting manually
emacs --daemon

# Then connect
emacsclient -c

Debugging

Evaluate Without Building

nix repl
:lf .
darwinConfigurations.default.config.home-manager.users.YOU.home.packages

Check Option Values

nix repl
:lf .
darwinConfigurations.default.config.programs.emacs.decknix.languages.kotlin.enable

Check Generated Emacs Config

find /nix/store -name "default.el" -path "*/emacs-packages-deps/*" 2>/dev/null | head -1 | xargs cat

Reset to Clean State

rm -rf ~/.config/decknix
# Re-run bootstrap or nix flake init -t github:ldeck/decknix

Configuration Hub

Your single entry point for configuring a decknix-managed system. Browse framework options, or jump to upstream references for home-manager, nix-darwin, packages, and casks.

JavaScript is required for the interactive configuration hub.

Pair Protocol v1 (draft)

Status: draft / pre-implementation. Reference implementation: UpsideRealty/experiment-ai-pairing (not yet created at time of writing). Last updated: 2026-05-29.

A protocol for multi-human and multi-agent collaborative sessions layered on top of an agent shell. Designed to be incrementally implementable, mode- switchable, and tolerant of mixed clients (Emacs, web, MCP, CLI, Slack).

This document is the design reference; the implementation is owned by the experiment repository above and may diverge ahead of doc updates while the project is still in its early phases. Where the two disagree, the implementation wins and a doc PR should follow.

1. Goals and non-goals

Goals

  • Multi-seat sessions where each seat is a (participant, role) pair; role is human or agent.
  • Mode-switchable governance — conversational today, driver/multi-driver later — without protocol breaks.
  • Two transcript surfaces: a live transcript over WebSocket/SSE, and a long-lived transcript mirrored to a Slack private channel.
  • A first-class artifact model — specs, scopes, contracts, architectures — persisted to a durable backend (GCS by default; adapters for Slack, Google Drive, filesystem, git).
  • An explicit promote action that takes finished artifacts and opens a draft pull request against the relevant code repository, with a polite request for augmentcode review baked into the workflow.
  • Zero-config join for MCP-capable agents; a thin CLI for everyone else.
  • Strong audit: every action attributable to a seat, every tool call logged.

Non-goals (v1)

  • External (non-staff) participants. Tailnet-only access in P1; Cloudflare Access can be added later if the need arises.
  • A fully merged "drive my keyboard" remote-control experience. The protocol reserves driver modes but P1 ships conversational only.
  • Replacing existing chat or code review tooling. Slack remains the human mirror; GitHub remains the code review surface.

2. Core concepts

2.1 Sessions and seats

A session is an addressable, time-bounded room with a unique id (ses_<ULID>). It has:

  • a host (the human who created it, holds admin rights in v1);
  • zero or more additional seats, each (participant_id, role) where role is human | agent;
  • a configured mode (see §2.3);
  • one or more configured artifact stores (see §5);
  • an optional Slack mirror channel (see §4.3);
  • an optional repo alias map for promote (see §6).

Seats are the unit of attribution: every event, message, and tool call carries the originating seat_id, never just the participant.

2.2 Participants

A participant is the identity behind a seat — a Slack user for humans, or a named agent registration for agents (e.g. alice/agent, where the agent runs on Alice's behalf). Identity is resolved against the existing Slack identity cache so the same human looks the same across Emacs, CLI, and web clients.

2.3 Modes

Modev1 statusBehaviour
conversationalshippedAll seats may speak; no exclusive driver; agent posts are throttled and addressed-only.
driver-singlereservedOne driver seat; others are readers and may post questions only.
driver-handoffreservedDriver may pass control to another seat by explicit gesture.
multi-driverreservedMultiple driver seats, admin-curated; readers may ask but not drive.

Mode is a session-level property; switching modes is an admin-only event. The wire protocol is identical across modes — only the policy gate (§7) differs.

2.4 Transports

The session is exposed over four transports, all of which see the same underlying event stream:

TransportAudience
WebSocket (live)Web UI, Emacs, CLI subscribers.
Server-Sent EventsLightweight read-only browser clients.
MCP server (per session)Agents (Claude Desktop, Augment, etc.).
Slack mirrorHumans for long-lived browsable history.

All inbound writes (post a message, publish an artifact, request a promotion) flow through the relay's policy gate before being broadcast.

3. Event envelope

Every event on the wire shares the same JSON envelope:

{
  "v": 1,
  "id": "evt_01J8A9X0M3T4VWQ0B1C2D3E4F5",  // ULID, server-assigned
  "ts": "2026-05-29T01:23:45.678Z",
  "session_id": "ses_01J7Z8KX4VFWQ0M9N6R2H3D8AB",
  "seat_id": "seat_01J7Z8L1...",
  "type": "message",                         // see §3.1
  "payload": { /* type-specific */ },
  "in_reply_to": "evt_..."                   // optional, for threading
}

The envelope is stable; payload shape is per type. Unknown types must be tolerated by clients (forward-compatibility).

3.1 Event types

TypePurpose
session_startedSession created; carries mode, host seat, configured stores.
seat_joinedNew seat enters the session.
seat_leftSeat exits (voluntary or evicted).
mode_changedAdmin changed the session mode.
messageFree-form text from a seat. Markdown-flavoured plain text.
tool_callAn agent seat ran a tool (name, args, result-hash).
flagA seat flagged a moment for human attention (e.g. a question).
artifact_publishedA seat published or superseded a session artifact (§5).
promotion_requestedA seat asked to open a PR with one or more artifacts (§6).
promotion_completedPromotion succeeded or failed; carries the PR URL on success.
auggie_review_requestedRelay posted the auggie review comment on a promoted PR (§6.4).
auggie_review_settledAugmentcode replied with feedback or "nothing to add" (§6.4).
pr_ready_for_reviewA draft PR was flipped to ready for human review (§6.4).
policy_deniedA request was blocked by the policy gate; carries a reason code.
auditAuxiliary audit record (rate-limit hit, admin action, etc.).

New types may be added; clients ignore unknown types.

3.2 Persistence

The relay writes every event to an append-only JSONL transcript at ~/.local/state/pair-relay/sessions/<session_id>.jsonl (or the equivalent on a VM). This is the canonical record. The Slack mirror and the WebSocket fan-out are both derived views. Transcripts are not committed to VCS; they live on disk and (optionally) in the Slack mirror.

4. Transports

4.1 WebSocket / SSE (live transcript)

  • One WebSocket endpoint per session, served by the relay over Tailscale.
  • Clients authenticate with a short-lived bearer token minted at invite time (see §8).
  • Server pushes events as they happen; clients may post inbound events of type message, flag, artifact_published, promotion_requested.
  • SSE is the read-only fallback for clients that can't open a WebSocket.

4.2 MCP (agent transport)

Each session exposes an MCP server at a session-specific URL. Agents join by pasting the URL plus bearer token into their MCP client config.

Tools exposed (initial set):

ToolEffect
pair.subscribeBegin receiving the live event stream.
pair.get_transcriptFetch the transcript (or a tail) for context.
pair.post_messagePost a message event (subject to policy).
pair.askPost a flag event explicitly tagged as a question for humans.
pair.whoList current seats.
pair.publish_artifactUpload bytes and emit artifact_published.
pair.promote_artifactRequest a promotion (§6); subject to admin approval.

Agents must obey the policy gate (§7); the relay enforces it independent of client co-operation.

4.3 Slack mirror (long-lived transcript)

When a Slack channel is bound at session creation, the relay mirrors a human-readable subset of events into it:

  • message (rendered with the seat as the author),
  • flag (rendered as a quoted question with a notification ping),
  • artifact_published (file upload with a link to the canonical store),
  • promotion_requested / promotion_completed (PR link as a pinned message on completion),
  • auggie_review_settled and pr_ready_for_review (status updates).

Tool calls and low-level audit events are not mirrored by default; they remain in the JSONL transcript for forensic use.

Humans may type slash commands into the channel:

/pair flag <text>
/pair publish <name> <text>
/pair promote <artifact-ref> to <repo> [as <path>]

The Slack bot parses these, authenticates the user against the bound session, and emits the corresponding event on their seat's behalf.

4.4 CLI (pair)

A thin Rust CLI for humans who prefer the terminal. Same operations as the slash commands plus session lifecycle:

pair create [--mode conversational] [--mirror-channel <id>] [--store <kind>:<config>]
pair join <session-id>
pair say <text>
pair flag <text>
pair publish <file> [--name <name>] [--kind <kind>]
pair promote <artifact-ref>... --to <repo> [--as <path>] [--title <s>] [--draft]
pair list
pair watch <session-id>

The CLI talks to the relay over the same WebSocket endpoint as the web UI.

5. Artifacts and durable storage

5.1 What an artifact is

An artifact is a named, typed blob produced during a session — a scope, a spec, a contract, an architecture sketch, sometimes a transcript snapshot. Artifacts carry:

  • id — opaque (art_<ULID>);
  • name — human label, unique within a session;
  • kindscope | spec | contract | architecture | transcript | other;
  • bytes — opaque content;
  • meta — content_type, summary, publishing_seat, optional supersedes;
  • ref — backend-opaque locator returned by the store.

Artifacts are explicitly session outputs, not conversation history. They want durable, shareable, queryable storage — not git unless the content is genuinely document-like and worth diffing.

5.2 The ArtifactStore port

The relay holds artifacts through a trait-shaped adapter:

#![allow(unused)]
fn main() {
trait ArtifactStore {
    async fn put(
        &self,
        session_id: &SessionId,
        name: &str,
        kind: ArtifactKind,
        bytes: &[u8],
        meta: ArtifactMeta,
    ) -> Result<ArtifactRef>;

    async fn get(&self, r: &ArtifactRef) -> Result<Vec<u8>>;
    async fn list(&self, session_id: &SessionId) -> Result<Vec<ArtifactRef>>;
    async fn url(&self, r: &ArtifactRef) -> Result<String>;
}
}

A session may configure one or more stores; the relay writes to all of them in order on put and reads from the first that resolves on get.

5.3 Built-in adapters

AdapterBackingFits when…
gcsGoogle Cloud Storage bucketDefault for NurtureCloud. Versioned objects, IAM via corporate Google.
s3AWS S3Same shape on AWS.
slackA bound channel; artifacts as uploadsLowest-friction for small/transient sessions.
gdriveA shared Drive folderNon-engineer participants want preview + native sharing.
fsA local (possibly synced) directoryLaptop relay; quick dev/test.
gitAny git repoOpt-in when an artifact will evolve and benefits from named diffs.

The protocol is store-agnostic; sessions declare what they want:

[[artifact_store]]
kind   = "gcs"
bucket = "nc-pair-artifacts"
prefix = "<session_id>/"

# Optional second store, written in parallel for human convenience:
[[artifact_store]]
kind        = "slack"
channel_id  = "C0XXXXXXXXX"

5.4 Defaults for NurtureCloud sessions

  • Primary store: gcs against a bucket like nc-pair-artifacts, object versioning on, lifecycle rule to move >90-day objects to colder storage.
  • Secondary store (always on by default): the same Slack channel bound for the transcript mirror. The Slack message carries a link back to the canonical GCS object.
  • Filesystem fallback is used when the relay runs in laptop mode without GCS credentials (see §10, Phase D1).

The Slack channel becomes the human-discoverable index; GCS is the durable archive.

6. Promotion — opening a PR with finished artifacts

6.1 What promotion is

promote is the explicit gesture that takes one or more session artifacts and opens a draft pull request against a real code repository. It is the only protocol-defined integration between the pair tool and a code repo; everything else stays out of VCS.

Conceptually:

promote(artifact_ids: [Id], target_repo: Repo, target_path: Path,
        branch?: String, title?: String, body?: String, draft?: bool = true)
  -> PullRequestRef

6.2 Surfaces

Slash command (web UI and Slack mirror):

/pair promote <artifact-ref> to <repo> [as <path>] [titled "..."] [--draft|--ready]
/pair promote @last to upside as docs/specs/pair-protocol.md
/pair promote @kind:spec to upside           # bulk by kind
/pair promote scope-pair-protocol-v1 to nct-public-api as docs/scopes/

Artifact refs accepted: art_<ULID>, name, @last, @last:N, @all, @kind:<kind>.

CLI:

pair promote <ref>... --to <repo> [--as <path>] [--branch <name>] \
                      [--title "..."] [--body-from <file>] [--draft|--ready]

MCP tool: pair.promote_artifact (same shape as the CLI).

6.3 Per-session repo aliases

Sessions may declare friendly repo aliases at create time so to upside resolves correctly:

[repos]
upside          = "UpsideRealty/upside"
nct-public-api  = "UpsideRealty/nct-public-api"
reinz           = "UpsideRealty/nct-provider-reinz-service"
experiment      = "UpsideRealty/experiment-ai-pairing"

Aliases are resolved against this map; fully-qualified org/repo is always accepted.

6.4 The draft → augmentcode → ready workflow

Every promotion follows the same polite, reviewer-friendly loop. The tone across all relay-authored comments is soft and collaborative: the relay is asking for help, not issuing orders.

Step 1 — open as draft. Promotions default to draft = true. The PR title is taken from --title or derived from the artifact name; the body summarises which artifacts landed and links back to the session.

Step 2 — invite augmentcode. Immediately after the draft PR is open (and again after every subsequent push to the PR's branch), the relay posts an auggie review comment. The exact phrasing is fixed so augmentcode picks it up reliably; surrounding text stays gentle:

Hi @augmentcode — when you have a moment, would you mind taking a look?

auggie review

The relay emits an auggie_review_requested event so the session sees that review has been asked for.

Step 3 — wait for augmentcode to settle. The relay polls the PR for new review comments from augmentcode until one of two terminal states is reached:

  • Comments to consider. Augmentcode left suggestions or questions. The relay emits auggie_review_settled { state: "has_feedback" } with a short summary plus a link to the review. The host or driver decides which to apply as fixes and which to politely decline (with a reply explaining the reasoning). After any fix is pushed, Step 2 fires again.
  • Nothing to add. Augmentcode replied that it has no further feedback. The relay emits auggie_review_settled { state: "clean" } and proceeds to Step 4.

Step 4 — flip to ready. Once augmentcode is clean, the relay marks the PR ready for human review and emits pr_ready_for_review. The Slack mirror posts the PR link with a brief, polite note such as:

This one is ready for human eyes when you have time — thank you!

The relay does not request specific human reviewers automatically; that remains a host gesture (or a follow-up Slack post). Human reviewer assignment via --reviewer @handle on the original promote is a reserved future option.

Tone rule. All relay-generated PR text — bodies, comments, replies to augmentcode — uses soft, collaborative language: "would you mind", "happy to revise", "thanks for taking a look". No directive or adversarial phrasing.

6.5 Commit and PR shape

Commits opened by the relay carry attribution trailers so the human and the requesting seat are both visible:

Author:  Lachlan Deck <lachlan@nurturecloud.com>
Pair-Session: ses_01J7Z8KX4VFWQ0M9N6R2H3D8AB
Pair-Promoted-By: lachlan/human
Pair-Requested-By: alice/agent
Pair-Artifacts: art_01J7..., art_01J7...

Per workspace policy, the relay does not add Co-authored-by: footers naming itself or augmentcode.

PR body skeleton:

This PR promotes the following artifacts from pair session
[`ses_01J7Z8KX...`](relay-url):

- `scope-pair-protocol-v1.md` → `docs/scopes/pair-protocol-v1.md`
- `spec-pair-protocol-v1.md` → `docs/specs/pair-protocol-v1.md`

It is open as a draft so augmentcode can take a first pass; once that
review settles it will be flipped to ready.

Thanks for taking a look.

6.6 Credentials — phased

PhaseMechanismNotes
D1Relay shells out to the host's local gh CLI.Laptop mode only; reuses existing host auth; trivially safe.
D2A GitHub App in the UpsideRealty org with contents:write and pull_requests:write.Tailnet VM mode. App opens the PR; trailers credit the human + seat.
D3Same App with finer scopes; per-user OAuth for non-host promoters if needed.Only if external participants ever land.

Start at D1. Migrate to D2 when the relay moves off the laptop.

6.7 Direct push for experiment- repos

For repositories whose name starts with experiment- (e.g. UpsideRealty/experiment-ai-pairing), the relay accepts an additional --direct flag on promote that commits straight to the repo's default branch without opening a PR. This is reserved for the implementation repo itself while it iterates rapidly. If the project graduates out of experiment- status (rename) the flag stops being honoured.

--direct and --draft are mutually exclusive; --direct skips the augmentcode loop entirely. The commit trailers and tone rules of §6.5 still apply.

7. Governance and policy gate

Every inbound write passes through the relay's policy gate before being broadcast. The gate is the only enforcement boundary; clients are not trusted.

7.1 Decisions

Per (action, mode, seat-role) the gate decides one of:

  • ALLOW — broadcast and persist.
  • DEFER → host — emit a flag to the host; broadcast only after the host's pair admit event.
  • DENY — emit a policy_denied event with a reason code; do not broadcast.

7.2 Matrix (v1)

Action / Modeconversationaldriver-singledriver-handoffmulti-driver
message (human, non-driver)ALLOWALLOWALLOWALLOW
message (agent, addressed-to-it)ALLOWALLOWALLOWALLOW
message (agent, unaddressed)RATE-LIMITEDDENYDENYRATE-LIMITED
flag (any seat)ALLOWALLOWALLOWALLOW
artifact_published (any seat)ALLOWDRIVER ONLYDRIVER ONLYDRIVER ONLY
promotion_requested (host-human)ALLOWALLOWALLOWALLOW
promotion_requested (driver, any role)n/aDEFER → hostDEFER → hostDEFER → host
promotion_requested (other seat / agent)DEFER → hostDEFER → hostDEFER → hostDEFER → host
mode_changed (any seat)HOST ONLYHOST ONLYHOST ONLYHOST ONLY
evict / admitHOST ONLYHOST ONLYHOST ONLYHOST ONLY

Admin actions (mode_changed, evict, admit, and approval of any DEFER → host decision) are restricted to the host-human seat in v1. A future toggle may relax this to designated admin seats.

7.3 Defaults that protect against agent noise

  • Agents default to "silent + addressed-only": an agent only posts when a message is addressed to its handle, or when its host has asked it to opine via pair.post_message with an explicit gesture.
  • Rate limits per agent seat: at most one message per N seconds (configurable; default 5s), with bursts up to 3 events.
  • Loop guard (§9) prevents agents from auto-responding to other agents' messages.

8. Identity, auth, and network perimeter

8.1 Identity

Humans authenticate against Slack OIDC; their Slack user id is the canonical participant id. Agent participants register with a relay-issued handle of the form <human>/agent (e.g. alice/agent) so each agent is tied to a responsible human.

8.2 Tokens

Joining a session uses a short-lived bearer token (default TTL 24h) minted by the relay at invite time. Tokens are scoped to a single (session_id, seat_id) pair. The relay rotates the signing key on a fixed cadence.

8.3 Network perimeter

PhaseExposureNotes
D1localhost only (laptop launchd)Host uses the relay directly; no remote participants.
D2Tailscale (tailnet members only)Default for in-team pairing. ACLs restrict to staff.
D3Cloudflare Access in front of D2Only if external participants ever need to join.

P1 ships D1 with a clear migration path to D2.

9. Loop guard and rate limiting

Two relay-side mechanisms prevent agent storms:

  1. Loop guard. An agent seat may not respond to a message whose originating seat is also an agent unless that message explicitly @-mentions the responding agent's handle. The gate enforces this independent of client co-operation.
  2. Token-bucket rate limiting. Each agent seat has a bucket (default: 3 token capacity, refill 1 token every 5s). Exhausted buckets cause message events to be dropped with a policy_denied event of reason rate_limited.

Both can be tuned per-session by the host; sane defaults ship in P1.

10. Phasing and roadmap

10.1 Protocol phasing (P*)

PhaseScopeTarget
P0This spec, repo bootstrap (UpsideRealty/experiment-ai-pairing), CI skeleton.This week.
P1Relay daemon (Rust), conversational mode only, JSONL transcript, WebSocket transport, single-host launchd, basic web UI.This week / next.
P2Slack mirror, GcsArtifactStore + FilesystemArtifactStore, publish_artifact, MCP transport.Next.
P3pair CLI parity with web UI; slack artifact adapter; identity polish.Following.
P3.5promote command end-to-end, including the augmentcode review loop (§6.4). Starts on D1 credentials.Following.
P4Driver modes (driver-single, driver-handoff), eviction, admin polish.When wanted.
P5multi-driver, gdrive and git artifact adapters, optional Cloudflare exposure (D3).Later.

P1 is the smallest thing that lets two people pair through the relay in conversational mode with a JSONL transcript on disk. Everything else plugs in behind the stable interfaces above.

10.2 Deployment phasing (D*)

D1 (laptop launchd) → D2 (tailnet VM) → D3 (Cloudflare Access on top). These are independent of the protocol phases; D1 is the only one P1 requires.

11. Defaults summary

ConcernDefault
Modeconversational
AdminHost-human seat only
Live transportWebSocket (SSE fallback)
Agent transportMCP
Slack mirrorEnabled when a channel is bound; carries human-readable subset
Primary artifact storegcs (nc-pair-artifacts bucket, versioning on)
Secondary artifact storeslack (bound channel, file upload with link to canonical store)
Promotion drafttrue
Augmentcode reviewAlways requested on every push to a promoted PR (auggie review comment)
Ready-flipManual gesture if augmentcode has feedback; automatic when augmentcode is clean
Network perimeterD1 (localhost) for P1; D2 (Tailscale) when off-laptop
Agent rate limit3 burst, 1 token / 5s refill
Loop guardOn
Event idsULID
Implementation languageRust (relay + CLI)
Implementation repoUpsideRealty/experiment-ai-pairing (direct-push allowed while named experiment-)

12. Open questions

These remain for the next round (none of them block P1):

  1. Promoted-PR human-reviewer assignment. Should pair promote --reviewer @handle (or a per-session default list) become part of P3.5, or do we keep that as a manual step after the relay flips the PR to ready?
  2. Multi-store consistency. When gcs and slack are both configured and the Slack write fails after the GCS write succeeds, should the relay retry, log-and-continue, or surface a visible warning to the session? Default proposal: log-and-continue with an audit event.
  3. Augmentcode "settled" detection. What's the most robust signal for "augmentcode has nothing further to add"? Options include a sentinel comment from the bot, a review with no comments and no change requests, or an explicit phrase match. Implementation will pick the most reliable available at the time and document it.
  4. Driver hand-off gesture. P4 territory — slash command or button in the web UI? Both eventually, but which lands first?

13. Glossary

TermMeaning
SessionAn addressable, time-bounded pair room with a stable id.
SeatA (participant, role) tuple; the unit of attribution.
ParticipantThe identity behind one or more seats (a Slack user, or an agent registration).
ModeThe governance state of a session (conversational, driver-*, multi-driver).
HostThe human who created the session; in v1 also the sole admin.
DriverA seat with write-control authority in driver modes (not used in v1).
ReaderA seat without write-control authority in driver modes (not used in v1).
ArtifactA named, typed blob produced during a session.
Artifact storeBackend that durably persists artifacts; one of gcs/s3/slack/gdrive/fs/git.
PromoteExplicit action that opens a draft PR in a code repo from one or more artifacts.
Auggie reviewThe auggie review comment the relay posts to invite augmentcode feedback.
Policy gateRelay-side enforcement layer that decides ALLOW / DEFER / DENY on every inbound write.
Loop guardPrevents agents from auto-replying to other agents without an @-mention.
D1 / D2 / D3Deployment phases (laptop / tailnet VM / Cloudflare Access).
P0 / P1 / …Protocol implementation phases.

External Resources

Nix

nix-darwin

Home Manager

Emacs

Window Management

AI Tooling

Decknix