A Rust terminal LLM assistant: shell-error analysis + an SRE chat agent that runs bounded, sandboxed read-only diagnostics. Works with OpenAI-compatible, Groq, Anthropic, and CLI backends.
Languages: English · 한국어
When a command fails, aic hands its output to an LLM and gets back an explanation of what went wrong plus a suggested fix.
It works through a PTY-based daemon (aic-session) that wraps your shell, relaying I/O while keeping a ring buffer of recent output. The CLI client (aic) reads the previous command's exit code and either explains the error or drops you into an interactive REPL.
A per-user supervisor daemon (aicd) manages session lifecycle, registry, and cleanup. For workflows where PTY wrapping is too expensive, a metadata-only hook capture mode skips output capture entirely (PRDs: docs/PRD-AICD-SUPERVISOR.md, docs/PRD-HOOK-CAPTURE-MODE.md).
graph LR
User[User Terminal] --> Session[aic-session]
Session -->|PTY relay| Shell[Shell zsh/bash]
Session -->|capture output| RB[Ring Buffer]
Session -.register/unregister.-> AICD[aicd supervisor]
Hook[shell hook] -.metadata only.-> AICD
Client[aic] -->|UDS| Session
Client -->|control UDS| AICD
Client -->|error analysis| LLM[LLM Provider]
- ✅ PTY shell wrapper — captures output without changing your workflow
- ✅ Command boundary detection — OSC 133 markers + timing-heuristic fallback
- ✅ Automatic error analysis — when exit code ≠ 0, the LLM explains the cause and suggests fixes
- ✅ Interactive REPL — when exit code = 0, freeform chat with the LLM
- ✅
aic chatagent mode — explicit chat entry point. With an OpenAI-compatible provider it runs a tool-calling agent over your project; gracefully degrades to plain chat when the provider doesn't support tools - ✅ SRE shell execution (default-on) — the interactive agent can run bounded shell commands via
run_command. Read-only diagnostics run automatically and may inspect the whole host (e.g.tail /var/log/...,du -ah /tmp | sort -rh | head,find /tmp -mmin -10); state-changing commands need confirmation and dangerous ones are blocked. Secret paths (~/.ssh,~/.aws,/etc/shadow,*.pem,.env, …) are blocked even for reads. Turn it off with--no-run/--read-only/AIC_AGENT_NO_RUN=1for a read-only session (read_file/list_dir/grep/globonly) - ✅ Multiple LLM providers — OpenAI-compatible, Groq, Anthropic, CLI Backend (kiro-cli, claude-cli)
- ✅ MCP tool servers —
aic chatcan call tools from configured MCP servers (e.g. mem-mesh memory) over Streamable HTTP; discovered tools join the agent under<server>__<tool>names, read-only ones (inauto_approve) run automatically and mutating ones require confirmation. Config:[mcp.servers.<name>](see Configuration) - ✅ TUI compatibility — alternate-screen-buffer detection keeps vim, htop, etc. working correctly
- ✅ Cross-platform — macOS (Apple Silicon, x86_64), Linux (x86_64, aarch64)
- ✅ Single-instance guarantee —
fcntl(F_SETLK)PID lock with automatic stale cleanup - ✅ Graceful shutdown — SIGTERM/SIGINT handling, drain then cleanup
- ✅ Structured trace logs — JSONL daily-rotate (7-day retention),
AIC_LOG=info|debug - ✅
aic doctor— 9-axis environment diagnosis (config / provider / socket / daemon / supervisor / shell hook / LLM endpoint / keychain / audit) - ✅
aic status— daemon PID / ping / last command, one-shot output - ✅ Proactive chat status bar — the
aic chatstatus line samples host metrics in an off-thread task (so a hung mount or an idle prompt never freezes the UI) and surfaces problems live: severity-colored segments, a per-metric sparkline + trend arrow, a gated disk-exhaustion ETA (disk 4.2G free · ~8m→crit), and edge-triggered alerts that name the top offending process (⚠ mem 97% — top: node 12.1G) with hysteresis/cooldown. Toggle the alert lane with/watch arm|off - ✅
aic diagnose— symptom-driven Safe probes → typed Findings (severity / confidence / probe_id). A deterministic threshold scan flags disk / inode / fd / swap exhaustion, kernel OOM-kills, and failed systemd units without an LLM;aic diagnose --jsonemits a machine-readable envelope - ✅
aic rca— persistent RCA workspace: incidents under~/.aic/incidents/<id>/(evidence.jsonl+report.md), withstart/status/timeline/report;--diagnoseattaches first evidence from the headless/diagnoseengine - ✅ Session snapshot recorder (opt-in) — background system snapshots to
~/.aic/snapshots/, gated byAIC_SNAPSHOT_RECORD: alert-triggered full capture (L1), a periodic timer (L2,aic snapshot install), and Crit auto-RCA (L3,AIC_AUTO_RCA). See Session snapshot recorder
- ✅ Secret/PII redaction — automatic masking for 5 secret types (AWS / GitHub / OpenAI / Anthropic / JWT) and 4 PII types (email / KR phone / KR resident number / IPv4); opt-out via
AIC_REDACT=off - ✅ Read-only host diagnostics with a secret-path denylist —
run_commandread-only commands may read host-wide (logs,/tmp,/proc), but secret paths are denied even for reads:~/.ssh/~/.aws/~/.gnupg/~/.kube/~/.docker,/etc/shadow,/etc/ssl/private,/proc/*/environ, and*.pem/*.key/.env/id_rsa/credentials(symlink targets resolved viacanonicalize). Egress (curl/ssh/nc) and mutation (rm/mv/docker prune) stay gated (confirm/block), and mutations remain confined to the cwd sandbox - ✅ Audit log HMAC chain —
~/.local/state/aic/audit.logJSONL append-only, integrity verification viaaic audit verify. The HMAC key uses a file backend by default; the OS keychain is opt-in (AIC_AUDIT_KEYCHAIN=1), andAIC_NO_KEYCHAIN=1forces it off- Upgrade note: if you used an earlier version where the audit key lived only in the OS keychain, the new file-backend default may report a verify WARN/missing-key (or skip new appends to protect the chain). Either keep verifying/using the keychain-backed chain by running with
AIC_AUDIT_KEYCHAIN=1, or start a fresh file-backed chain by backing up/rotating~/.local/state/aic/audit.logand re-running.aic doctorprints both options.
- Upgrade note: if you used an earlier version where the audit key lived only in the OS keychain, the new file-backend default may report a verify WARN/missing-key (or skip new appends to protect the chain). Either keep verifying/using the keychain-backed chain by running with
- ✅ OS keychain — store API keys in macOS Keychain / Linux Secret Service / Windows Credential Manager; bulk migrate plaintext via
aic migrate-keys
- ✅ Streaming — token-by-token streaming for OpenAI-compatible and Anthropic providers, including the
aic chattool-calling agent loop (spinner until the first token → live raw preview → formatted answer on completion); TTY only, opt-out viaAIC_NO_STREAM=1 - ✅ Result cache — same (cmd, exit, output) for 24h TTL, instant response
- ✅ Dry-run preview —
aic --dry-run "..."previews tokens, cost, and timeout in advance - ✅ Retry circuit breaker — after 5 failures within a 60s window, fail-fast for 30s
- ✅ i18n auto-detect — when
lang = "auto", infer from$LC_ALL/$LANG
- ✅
aic init zsh|bash— automatic shell-hook installation (idempotent via markers) - ✅
aic init --hook-mode— additionally install Phase 3 metadata-only hook - ✅
aic configinteractive wizard
- ✅
aicdsupervisor daemon — one per user. Session registry, control UDS, graceful shutdown - ✅
aic daemon { status | start | stop }— supervisor control - ✅
aic session stop <id>— registry-backed session termination - ✅
aic sessions— aicd registry-first, fallback to socket scan - ✅ Hook capture mode — collects metadata only via
~/.aic/hook-events.{zsh,bash} - ✅
aic run -- <cmd>— explicit FullOutput capture wrapper - ✅
CommandRecord.capture_mode/quality+ capture-quality hint during analysis
- 🚧
aic-proxy— LLM API proxy server (planned) - 🚧 Move PTY ownership into
aicd(full implementation of PRD-AICD-SUPERVISOR Phase 2) - 🚧
aic capture-last— destructive-command detection + confirm UX - 🚧 Automatic launchd / systemd unit installation
aic-sessionspawns your default shell as a PTY child process- Shell I/O passes through while an ANSI-stripped clean-text copy goes into a ring buffer
- OSC 133 markers (or a timing heuristic) identify command boundaries and produce a
CommandRecord - When you run
aic, it queries the previous command's data via UDS - Based on exit code it auto-branches into error analysis (LLM) or an interactive REPL
- Rust 1.75+ (2021 edition)
- macOS or Linux
- An LLM API key (OpenAI, Anthropic, Groq, etc.) or a CLI Backend (kiro-cli, claude-cli)
curl -fsSL https://raw.githubusercontent.com/x-mesh/aic/main/install.sh | shDetects OS/arch (linux/darwin × amd64/arm64), downloads the
matching release archive, verifies its SHA-256 against the published
checksums.txt, and installs aic + aic-session + aicd to
/usr/local/bin (with sudo fallback) or ~/.local/bin.
Override targets:
AIC_VERSION=v0.4.0 sh install.sh # pin a specific tag
AIC_INSTALL_DIR=$HOME/.local/bin sh ... # install to a user dirAfter installing, enable autostart once: aic daemon install
(auto-branches between macOS launchd and Linux systemd user unit).
brew tap x-mesh/tap
brew install aic
# Enable autostart, once after install:
aic daemon install # auto-branches between macOS launchd and Linux systemd user unitbrew services works well with macOS launchd but its Linux-systemd
support is spotty, so aic daemon install handles both OSes consistently.
git clone https://github.com/x-mesh/aic.git && cd aic
cargo build --workspace --release
cargo install --path aic-server # installs aic-session + aicd
cargo install --path aic-client # installs aicOr via Makefile:
make installDevelop / verify:
cargo check --workspace # fast type-check
cargo test --workspace # run the test suite (run before every commit)
make check # fmt + clippy + check (see Makefile)aic update # detect install source and upgrade in place
aic update --check # exit 1 if a newer release is available, 0 otherwise
aic update --to v0.4.0 # pin a specific tag (manual installs only)
aic update --force # reinstall even if already on the latest versionaic update detects how aic was installed and chooses the right path:
| Install source | Action |
|---|---|
Homebrew (/opt/homebrew, /usr/local/Cellar, linuxbrew) |
forwards to brew upgrade x-mesh/tap/aic |
Manual / install.sh (/usr/local/bin, ~/.local/bin) |
downloads + verifies sha256 + atomic-replaces all 3 binaries (sudo fallback for /usr/local/bin) |
cargo install (~/.cargo/bin) |
refuses self-replace, prints the equivalent cargo install command |
After upgrading the binaries on disk, restart aicd to pick up the new
version: aic daemon restart.
mkdir -p ~/.config/aic
cp <<'EOF' > ~/.config/aic/config.toml
[server]
max_buffer_lines = 500
[server.boundary_strategy]
method = "prompt_marker"
[llm]
default_provider = "openai"
[llm.providers.openai]
provider_type = "OpenAiCompatible"
endpoint = "https://api.openai.com/v1/chat/completions"
api_key = "sk-..."
model = "gpt-4o"
EOF# 1. First-time setup — config + automatic shell-hook install
aic config # interactive provider/api_key/model setup
aic init zsh # idempotently appends 'source ~/.aic/hooks.zsh' to ~/.zshrc
aic migrate-keys # move plaintext API keys into the OS keychain (optional)
aic doctor # 9-axis diagnosis — see PASS/WARN/FAIL at a glance
aic doctor --probe-tools # opt-in live probe: does the provider actually support tool-calling?
# 2. (optional) Start the supervisor — central multi-session lifecycle
aic daemon start # spawns aicd in the background
aic daemon status # check liveness + registered session count
# 3. Start a shell with aic-session — auto-registers if aicd is up
aic-session
# 4. Use commands as usual
cargo build # error!
# 5. Analyze the error with aic
aic
# → LLM explains the cause and suggests fix commands (auto-streams in TTY)
# 6. Running aic with no error → REPL mode
aic
# → Freeform chat with the LLM (exit/quit/Ctrl+D to leave)
# 7. Direct question + dry-run for cost preview
aic --dry-run "how do I fix this error?"
# 8. Explicit chat / agent mode (independent of exit code)
aic chat "summarize what this repo does" # one-shot answer, then exit
aic chat # interactive SRE agent (run_command default-on)
# → With an OpenAI-compatible provider, the agent reads your project via file tools
# (read_file/list_dir/grep/glob), confined to the cwd sandbox and honoring
# .gitignore. It can also run BOUNDED shell commands (run_command):
aic chat # then type: ps → runs `ps aux | head -n 20`
# disk → runs `df -h`
# cpu/memory/net → bounded OS-friendly command
# → Safe read-only commands run automatically and may inspect the WHOLE host
# (e.g. `tail /var/log/syslog`, `du -ah /tmp | sort -rh | head`, `find /tmp -mmin -10`),
# except secret paths (~/.ssh, ~/.aws, /etc/shadow, *.pem, .env) which are blocked.
# State-changing commands ask for confirmation (TTY); dangerous/unknown are blocked;
# mutations stay confined to the cwd sandbox. The shell is restricted
# (no $, globs, quotes, redirects, ;, &, pipes-of-danger).
aic chat --no-run # read-only session (no run_command); --read-only is a synonym
AIC_AGENT_NO_RUN=1 aic chat # same opt-out via env
AIC_DEBUG=1 aic chat # stderr debug: tool_specs/run_command/provider_tools (banner still shown)
NO_COLOR=1 aic chat # plain output (no ANSI; also auto on non-TTY)
# → On start, a banner + status line (mode/tools/policy/cwd/provider) prints to stderr.
# The chat prompt is "◇ you ❯ " on a TTY, plain "you> " when piped. LLM answers go
# to stdout; banner/status/command-cards/debug go to stderr (clean piping).
# → In-session slash commands are intercepted locally and never sent to the LLM:
# /local /diagnose /explain-last /incident /rca /doctor /timeline /compare /record /snapshots /bundle /triage /watch /help.
# Type "/" on a TTY to open the completion panel. See "Chat slash commands" below for the full table.
# (legacy flags --sre / --allow-run still parse but are now no-ops: run_command is on by default)
# Design: docs/PRD-AIC-SRE-CHAT.md · docs/RFC-002-AIC-CHAT-AGENTIC.md
# → Preview cost with: aic chat --dry-run "ping"
# 9. Operations
aic status # daemon PID / ping / last command
aic sessions # all active sessions (aicd registry-first)
aic session stop <id> # terminate a specific session (requires aicd)
aic audit verify # audit-log HMAC-chain integrity (exit 0/2/3)
aic diagnose <symptom> # symptom-driven read-only diagnosis (add --json for machine output)
aic rca status # persistent RCA incidents (start / status / timeline / report)
aic snapshot status # session snapshot recorder (capture / list / status / install / uninstall)aic rca persists root-cause analysis per incident id. Evidence goes to
~/.aic/incidents/<id>/evidence.jsonl and the report to report.md in the same directory (evidence
files 0600, incident dirs 0700).
# create an incident workspace
aic rca start "api latency" --symptom "p99 latency spike"
# attach Safe-probe evidence right after creation
aic rca start "disk full" --diagnose --no-analyze
# recent incidents / status
aic rca status
aic rca status <id-prefix>
# chronological evidence timeline
aic rca timeline <id-prefix>
# markdown report with evidence ids ([E1], [E2]…)
aic rca report <id-prefix> --writeP0 scope is start/status/timeline/report. --diagnose reuses the headless /diagnose engine to store
the first RCA evidence, and the report cross-references its conclusions back to evidence ids in an
appendix. Inside aic chat, /rca start|use|add|timeline|report appends conversation evidence to the
same workspace.
Persistently record system snapshots to ~/.aic/snapshots/ so you can look back at what the host looked
like before an incident. Opt-in — nothing is written unless AIC_SNAPSHOT_RECORD=1 (or /record on inside a chat). Each snapshot is the same redacted evidence as /local, appended as JSONL (file
0600, newest 200 kept), in a silo separate from aic rca incidents.
Four layers, each independently gated:
- L0 —
/comparesnapshots are appended while recording is on. - L1 — the
aic chatstatus-bar sampler captures a full/localsnapshot when a resource worsens (Normal→Warn/Crit), off-thread so a hung mount never blocks the UI. - L2 — a periodic capture timer (
aic snapshot install, macOS launchd / Linux systemd-user) plus a manual CLI. - L3 — on a Crit transition,
AIC_AUTO_RCA=1auto-creates an RCA incident from collected evidence (no LLM call).
aic snapshot capture # one capture honoring the opt-in gate (--force ignores it)
aic snapshot list --json # recent snapshots (metadata envelope; bodies never printed)
aic snapshot status --json # recorder + timer status
aic snapshot install --interval 300 # install the periodic timer (interval clamped to ≥60s)
aic snapshot uninstallInside aic chat, /record [on|off|now] toggles recording for the session (now = capture once,
bypassing the gate) and /snapshots [N] lists the most recent N (default 10). While recording, a red
● REC segment leads the status bar. Concurrent writers (the off-thread capture vs. the session's
/compare append) are serialized by a process-internal mutex plus a cross-process flock, so no writes
are lost.
Inside aic chat, lines starting with / are intercepted locally — they are never sent to the LLM
and never enter the chat history; their output goes to the screen (stderr) only. On a TTY, typing /
opens a candidate panel (↑↓ to move, Tab to cycle, Enter to pick, Esc to close).
| Command | What it does |
|---|---|
/help |
List the available slash commands |
/last [N] |
Show the last tool card, or a compact list of the last N tool calls |
/raw [seq|corr] |
Full redacted output of the last (or a specific) tool call |
/local [section] [--raw] |
Local sysinfo snapshot → LLM summary (--raw = evidence only). alias: /sys, /snapshot |
/diagnose [--raw] <symptom> |
Pick Safe probes from the symptom, collect evidence, analyze → hypotheses / cited evidence / next safe checks |
/explain-last [--raw] [seq|corr] |
Analyze the last (or given) tool record: cause candidates / evidence / next checks |
/incident [--raw] [name] |
Bundle system snapshot + git read-only evidence (in a repo) + recent records, then analyze. name is a label only |
/doctor |
AIC self-status: provider/model, tool-calling support, run_command on/off, env flags as set/unset only (no secret values) |
/timeline [N] |
Session tool records in chronological order (redacted) |
/compare |
Diff a fixed-Safe system snapshot against the previous baseline (no LLM) |
/record [on|off|now] |
Toggle session snapshot recording (now = capture once, bypassing the gate). See Session snapshot recorder. While on, a red ● REC leads the status bar |
/snapshots [N] |
List the most recent N (default 10) recorded snapshots inline (metadata only; bodies never shown) |
/bundle [name] |
Save incident evidence as redacted markdown under ~/.aic/bundles/ (dir 0700 / file 0600 on Unix) |
/rca start|use|add|timeline|report |
Save chat evidence to a persistent RCA workspace. e.g. /rca start api-latency, /rca add last 3, /rca add note ..., /rca report --write. See RCA workspace |
/triage [--run] [topic] |
Topic checklist + candidate probes from the Probe Catalog; --run executes them (no LLM). topics: mac-slow web disk memory cpu network build-fail docker generic (the disk topic also checks docker disk usage and big /tmp files) |
/watch [target] [--count N] [--every Ns] |
Re-run probes a few times and summarize what changed per tick (no LLM). Bounded: default 3 runs (max 20), interval 1s. target is any Probe Catalog id — LOCAL sections, docker_df/docker_ps, tmp_big/tmp_recent — e.g. /watch tmp_recent tracks files growing under /tmp; omit it for a compact set |
/watch arm | /watch off |
Toggle the proactive alert lane (default on). When armed, a worsening resource transition (Normal→Warn/Crit) drops a one-line ambient note into the chat (Crit also rings a bell) and recovery prints a ✓ line. off/mute silences it. Distinct from the bounded-probe /watch <target> above |
Probes come from a single Probe Catalog (agent::probes) of fixed, bounded, read-only Safe commands:
local sysinfo sections (incl. fd = open file descriptors, current/max) + process + git read-only +
docker (docker_df/docker_ps/docker_images) + filesystem (tmp_big/tmp_recent). /local,
/compare, /diagnose, /incident, /bundle, and /triage all draw from it. The docker/filesystem
probes are not in the default /local set (they need docker / absolute-path reads) but are selected by
/triage, /diagnose, and /watch.
Analysis commands send a redacted evidence snapshot to the provider in a single, tool-less,
stateless call. --raw (and AIC_LOCAL_NO_ANALYZE=1) skip the model and show evidence only; on any
provider error/timeout they fall back to the raw evidence. Analysis output is rendered as a CLI-friendly
markdown subset (amber accents) on a TTY, plain when piped, with a progress spinner.
Deferred (roadmap): /runbook, /fix-preview, /config, a background watch daemon, persistent /audit browsing.
aic chat classifies every shell command before running it and never weakens the guard:
| Tier | Behavior | Examples |
|---|---|---|
| Safe | Runs automatically | ps aux, df -h, cat, grep, dig name |
| NeedsConfirm | TTY confirm (rejected when non-interactive) | systemctl restart, git commit, curl https://… (any network egress) |
| Dangerous | Blocked | rm -rf, mkfs, dd, ssh/scp/nc (remote/arbitrary network) |
| Unknown | Blocked (conservative) | unparseable / subshell $(…) |
Additional guarantees: commands run via sh -c confined to the cwd sandbox with a minimal env allowlist
(no API keys passed), bounded output + process-group timeout, and secret/PII redaction applied before
anything reaches the LLM, the screen, or the audit log. Disable shell execution entirely with --no-run /
--read-only / AIC_AGENT_NO_RUN=1 (read-only tools read_file/list_dir/grep/glob remain).
When you want to collect command metadata without paying the PTY-wrapping cost:
aic daemon start # aicd required (receives hook events)
aic init zsh --hook-mode # installs ~/.aic/hook-events.zsh
exec zsh # new shell → preexec/precmd hooks active
# Run commands as usual — metadata accumulates without aic-session
ls -la
cargo build
# Use explicit capture only when exact output is needed
aic run -- cargo build # preserves stdout/stderr and exit code| Variable | Effect |
|---|---|
| `AIC_LOG=info | debug |
AIC_REDACT=off |
disable secret/PII redaction (recorded in audit) |
AIC_NO_STREAM=1 |
disable token streaming (error analysis and the aic chat agent loop); show the full answer at once |
AIC_DEBUG=1 |
client emits [debug +X.XXXs] prefix |
AIC_AUDIT_KEYCHAIN=1 |
store the audit HMAC key in the OS keychain (opt-in). Default is a file key |
AIC_NO_KEYCHAIN=1 |
force keychain off (highest priority) — overrides the opt-in; always uses the file key |
AIC_LOCAL_NO_ANALYZE=1 |
skip analysis for /local·/diagnose etc.; show raw evidence only |
AIC_NO_BANNER=1 / AIC_QUIET=1 |
suppress the aic chat startup banner, status line, and context header (this chrome is unrelated to debug output) |
AIC_VERBOSE=1 |
show the detailed per-command run_command cards (preamble + → done summary). Default is quiet (only section headers + security warnings). AIC_DEBUG=1 also enables them |
AIC_SESSION_ID |
active session ID. Exported automatically by aic-session; hooks reference it too |
aic/
├── aic-common/ # shared data models, IPC protocol, errors
│ └── src/
│ ├── lib.rs # CommandRecord (+ capture_mode/quality),
│ │ # SessionInfo/SessionState, SessionConfig,
│ │ # AppConfig, capture_quality_hint()
│ ├── ipc.rs # IpcRequest/Response — session/control/hook
│ ├── error.rs # AicError
│ └── paths.rs # session_socket_path, aicd_socket_path,
│ # aicd_lock_path
├── aic-server/ # two binaries: aic-session + aicd
│ └── src/
│ ├── main.rs # aic-session: PTY wrapper + register/
│ │ # unregister to aicd
│ ├── aicd_main.rs # aicd: singleton + control UDS + signal
│ ├── control_server.rs # aicd control plane (RingBuffer-free)
│ ├── session_registry.rs # in-memory HashMap registry
│ ├── hook_events.rs # per-session bounded ring (Phase 3)
│ ├── aicd_client.rs # aic-session → aicd best-effort RPC
│ ├── pty_manager.rs / output_processor.rs / boundary_detector.rs /
│ │ ring_buffer.rs / uds_server.rs / lock.rs / metrics.rs / telemetry.rs
├── aic-client/ # CLI client (binary: aic)
│ └── src/
│ ├── main.rs # clap CLI: 11+ subcommands
│ ├── hook_install.rs # zsh/bash hook script generator (Phase 3)
│ ├── uds_client.rs # session UDS + aicd control client
│ ├── doctor.rs # 9-axis diagnosis (incl. aicd supervisor)
│ ├── config.rs / auto_brancher.rs / error_analyzer.rs /
│ │ llm_dispatcher.rs / repl.rs / cache.rs / redaction.rs /
│ │ audit.rs / keychain.rs / streaming.rs / spinner.rs / top.rs
├── docs/ # PRDs, capture-mode trade-offs
├── Cargo.toml # workspace definition
└── Makefile
Config file path: ~/.config/aic/config.toml (XDG Base Directory compliant)
[server]
max_buffer_lines = 500
# socket_path = "/custom/path/session.sock" # optional: override the socket path
[server.boundary_strategy]
method = "prompt_marker" # "prompt_marker" or "timing_heuristic"
# idle_threshold_ms = 500 # idle threshold when using timing_heuristic
[llm]
default_provider = "openai" # default provider name
# ── OpenAI-compatible (OpenAI, NVIDIA, etc.) ──
[llm.providers.openai]
provider_type = "OpenAiCompatible"
endpoint = "https://api.openai.com/v1/chat/completions"
api_key = "sk-..."
model = "gpt-4o"
[llm.providers.nvidia]
provider_type = "OpenAiCompatible"
endpoint = "https://integrate.api.nvidia.com/v1/chat/completions"
api_key = "nvapi-..."
model = "meta/llama-3.1-70b-instruct"
# ── Groq (OpenAI-compatible — defaults applied automatically when endpoint/model are omitted) ──
[llm.providers.groq]
provider_type = "Groq"
api_key = "gsk_..."
model = "llama-3.3-70b-versatile"
# When endpoint is omitted, https://api.groq.com/openai/v1/chat/completions is used.
# Other models: llama-3.1-8b-instant · deepseek-r1-distill-llama-70b · gemma2-9b-it
# ── Anthropic ──
# Model IDs: see https://docs.anthropic.com/en/docs/about-claude/models
# Recommended: claude-opus-4-7 (most capable), claude-sonnet-4-6 (balanced, default),
# claude-haiku-4-5-20251001 (cheap/fast).
# Older models (claude-sonnet-4-20250514, claude-3-5-haiku-20241022, etc.) may
# return 404 once retired — update to the IDs above.
[llm.providers.anthropic]
provider_type = "Anthropic"
endpoint = "https://api.anthropic.com/v1/messages"
api_key = "sk-ant-..."
model = "claude-sonnet-4-6"
# ── CLI Backend (local CLI tools) ──
[llm.providers.kiro-cli]
provider_type = "CliBackend"
cli_path = "kiro"
[llm.providers.claude-cli]
provider_type = "CliBackend"
cli_path = "claude"
# ── Observability backends (SRE) ──
# 등록된 백엔드만 질의 가능(endpoint allowlist) — LLM은 backend 이름만 고르고 URL은
# 직접 줄 수 없다. reqwest redirect 비활성 + link-local(169.254) 차단으로 SSRF를 막는다.
# aic chat의 tool-calling(prometheus_query/loki_query/es_search) + slash(/metrics, /logs)에서 사용.
[observability.backends.prom]
backend_type = "Prometheus" # VictoriaMetrics도 PromQL 호환이라 "Prometheus"로 등록
url = "http://prometheus:9090"
# auth = "keychain:obs_prom" # 선택: Bearer 토큰(평문 또는 keychain:<account> 참조)
[observability.backends.logs]
backend_type = "Loki"
url = "http://loki:3100"
[observability.backends.es]
backend_type = "Elasticsearch" # OpenSearch 포함
url = "http://elasticsearch:9200"관측 백엔드를 등록하면 aic chat에서 다음을 쓸 수 있다:
# slash 명령(LLM 미호출, redacted raw 출력) — backend가 타입별 1개면 -b 생략 가능
/metrics up
/metrics -b prom rate(http_requests_total[5m])
/logs {app="api"} |= "error"
# 또는 자연어로 물으면 에이전트가 prometheus_query/loki_query/es_search 도구를 호출한다.aic chat이 Model Context Protocol 서버(예: mem-mesh 메모리)의
tool을 직접 호출하게 한다. 현재 transport는 Streamable HTTP다. 등록하면 서버의 tool이
<server>__<tool> 이름으로 에이전트 tool 목록에 합류한다.
# ── MCP servers ──
# 각 서버의 tool이 chat tool-calling에 노출된다. 세션 시작 시 핸드셰이크(initialize/tools/list)로
# tool을 발견하며, 서버가 다운/지연이면 해당 서버만 건너뛰고 진행한다(graceful degrade).
[mcp.servers.mem-mesh]
url = "http://127.0.0.1:8787/mcp" # Streamable HTTP endpoint. obs와 동일한 SSRF 방어 적용
# enabled = true # 기본 true. false면 연결·노출 안 함
# auth = "keychain:mem-mesh" # 선택: Authorization: Bearer(평문 또는 keychain:<account>)
auto_approve = ["search", "context", "get_links", "stats"] # read-only tool은 확인 없이 자동 실행auto_approve에 적은 (read-only) tool은 자동 실행되고, 그 외(예:add/delete/update) 변경 tool은 실행 전 y/N 확인을 받는다(run_command와 동일 게이트).- tool 결과는 LLM에 넘기기 전 redaction + 길이 cap이 적용되고, 응답 크기도 bound된다.
- 등록하면 에이전트가 대화 중 알아서
mem-mesh__search로 과거 맥락을 찾거나mem-mesh__add로 결정을 저장할 수 있다(변경은 확인 후).
aicd가 Alertmanager/Grafana/PagerDuty/generic webhook을 수신해, firing alert마다
aic diagnose --bundle(읽기 전용 진단 + 증거 번들)을 자동 spawn한다. 온콜이 터미널을
열기 전에 증거가 준비된다. 기본 비활성 + 127.0.0.1 바인드다.
[aicd.webhook]
enabled = true # opt-in (기본 false)
listen_addr = "127.0.0.1:9099" # 기본 localhost. 외부 노출은 리버스 프록시 경유 권장
secret = "shared-secret" # 인증용. env AIC_WEBHOOK_SECRET가 우선
rate_limit_per_min = 10 # alert storm 비용 폭주 차단(token-bucket)
dedup_ttl_secs = 300 # 동일 fingerprint 재진단 차단(루프 방지)
auto_diagnose = true # alert 수신 시 aic diagnose 자동 spawn인증(secret 설정 시 둘 중 하나 필요):
Authorization: Bearer <secret>(Alertmanager/Grafana 헤더)X-AIC-Signature: <hex HMAC-SHA256(secret, body)>(PagerDuty류/generic)
엔드포인트: POST /webhook/alertmanager · /webhook/grafana · /webhook/pagerduty · /webhook(generic) · GET /health.
# Alertmanager receiver 예시
# webhook_configs:
# - url: http://127.0.0.1:9099/webhook/alertmanager
# http_config: { authorization: { credentials: "shared-secret" } }
aic webhook list # 수신·진단·dedup·rate-limit 이력 조회
aic webhook list --json # 스크립팅용기능별 실전 온콜 워크플로는 docs/SRE-USE-CASES.md, 설계 경계는 docs/SRE-SCOPE-BOUNDARY.md 참조.
aic는 TTY·GUI·인터넷이 없는 서버에서 1급으로 동작한다. CI의 headless job이 이 경로를
매 PR마다 검증한다(비대화 diagnose/audit/webhook + NeedsConfirm 비대화 거부).
- TTY 없음: cron/systemd/webhook spawn에서
aic diagnose·aic audit·aic webhook list가 hang 없이 동작한다. NeedsConfirm(상태 변경) 명령은 비대화 환경에서 자동 거부된다. - 키체인 없음: 헤드리스 Linux엔 Secret Service가 없을 수 있다.
AIC_NO_KEYCHAIN=1로 keychain을 건너뛰고 API key를 config 평문/환경변수로 쓴다. - air-gapped(인터넷 차단): 외부 LLM 대신 사내 OpenAI-compat 엔드포인트(vLLM/LiteLLM 등)를
[llm.providers.*]에 등록한다. 관측 백엔드·webhook도 전부 사내망 주소로 동작하므로 외부 송신 0으로 운영 가능하다.
# air-gapped: 사내 LLM + 사내 관측 백엔드만 사용
[llm.providers.internal]
provider_type = "OpenAiCompatible"
endpoint = "http://llm.internal:8000/v1/chat/completions"
api_key = "keychain:internal" # 또는 평문(headless면 env)
model = "qwen2.5-coder"| Variable | Description | Default |
|---|---|---|
XDG_CONFIG_HOME |
config-file directory | ~/.config |
XDG_RUNTIME_DIR |
socket path (Linux) | /tmp/aic-{uid} |
AIC_SESSION_ID |
active session identifier — aic-session exports it into the shell. Clients (aic/status/doctor/top) use it to locate the socket. |
(auto-generated) |
AIC_NO_RUN |
when set, disables the inline-run prompt for LLM-suggested commands | unset |
AIC_AUTO_RUN |
when 1, auto-runs without an inline-run prompt (excluding destructive commands) |
unset |
AIC_DEBUG |
when 1 or true, emits [debug +X.XXXs] logs to stderr (agent loop adds structured provider_tools=… / tool_specs=… lines; banner still shown) |
unset |
AIC_AGENT_NO_RUN |
when 1 or true, runs aic chat in read-only mode (disables run_command; same as --no-run/--read-only) |
unset |
NO_COLOR |
when set, suppresses ANSI colors in aic chat banner/status/cards/debug (also auto-suppressed on non-TTY stderr) |
unset |
AIC_REDACT |
when 1, masks secrets/PII in the prompt right before sending to the LLM |
unset |
AIC_NO_STREAM |
when set, disables streaming responses (received and displayed all at once) | unset |
Running aic-session from multiple terminals creates an independent socket for each.
| Platform | Path pattern |
|---|---|
| macOS | /tmp/aic-{uid}/session-{id}.sock |
| Linux (XDG set) | $XDG_RUNTIME_DIR/aic/session-{id}.sock |
| Linux (XDG unset) | /tmp/aic-{uid}/session-{id}.sock |
{id} is a 16-hex identifier auto-generated by aic-session (exported as the AIC_SESSION_ID env var).
How aic status / aic doctor / aic top etc. pick a session:
--session <id>(explicit argument)$AIC_SESSION_ID(shell export — typically automatic)config.server.socket_path(user override)- Most recently mtime-updated
session-*.sock(auto-pick the active session) - Legacy
session.sock(backwards compatibility)
Use aic sessions or aic status --all to see the full list.
JSON-over-UDS communication between server and client. Length-prefixed framing:
[4 bytes: payload length (u32 big-endian)][JSON payload]
Session daemon (aic-session) socket:
| Request | Description |
|---|---|
GetLastCommand |
retrieve the previous command's CommandRecord |
GetRecentLines { count } |
retrieve the last N lines of text |
Ping / GetMetrics |
health / metrics |
Supervisor (aicd) control socket:
| Request | Description |
|---|---|
Ping |
aicd health |
ListSessions |
every SessionInfo in the registry |
RegisterSession(SessionInfo) |
register a session (called by aic-session) |
UnregisterSession { id } |
deregister a session |
StopSession { id } |
SIGTERM the registry's PID |
Shutdown |
aicd graceful termination |
CommandStarted/Finished |
metadata events sent by the shell hook |
Sending to the wrong socket returns a graceful Error response ("connect to the aicd socket").
make # debug build
make release # release build (optimized)
make check # quick compile checkmake test # full test suite
make test-unit # unit tests only
make e2e # E2E tests only
make test-prop # property-based tests (1024 cases)
make test-pty # PTY integration tests (requires a terminal)make lint # clippy + fmt check
make fix # autofixmake run-server # run aic-session
make run-client # run aic
make run-config # run aic configmake ci # reproduce CI locally (lint + test)
make doc # generate and open rustdoc
make loc # lines-of-code statistics
make deps # dependency tree
make help # full command list| Area | Technology |
|---|---|
| Language | Rust (2021 edition) |
| PTY management | portable-pty |
| Async runtime | tokio |
| HTTP client | reqwest (rustls) |
| IPC | Unix Domain Socket (tokio::net::UnixListener) |
| Serialization | serde + serde_json / toml |
| CLI parsing | clap |
| ANSI stripping | strip-ansi-escapes |
| Testing | proptest (property-based testing) |
MIT (no LICENSE file is bundled yet)