Operations
This document covers local install, runtime lifecycle, parallel smoke testing, and diagnostics.
Install
One-line install:
curl -fsSL https://raw.githubusercontent.com/Lilac-Labs/gini-agent/main/scripts/install.sh | bashThe installer detects OS and arch, installs Bun if missing, clones the runtime into ~/.gini/runtime, installs dependencies, drops a gini wrapper at ~/.local/bin/gini, ensures ~/.local/bin is on PATH, and initializes the default instance under ~/.gini/instances/default/. The wrapper defaults GINI_INSTANCE=default (override via --instance or the GINI_INSTANCE env var) so installed users land on default. Repo-clone developers running bun run gini get an instance auto-derived from the repo directory basename so each worktree is isolated by default.
On macOS, the installer also enables autostart for the main instance (LaunchAgents at ~/Library/LaunchAgents/ai.lilaclabs.gini.main.gateway.plist and …main.web.plist) and opens the Gini webapp’s /setup page in your default browser. From the browser you pick a provider (OpenAI API key or Codex codex --login auth), submit, and the runtime starts using it on the next request. Pass --no-autostart to skip the LaunchAgent step (you’ll need to run gini start manually and visit /setup to configure a provider).
Why the browser flow: piped curl … | bash has no controlling terminal, so the legacy gini setup prompt would never run. The new flow makes the same UX work for both interactive and piped installs — the runtime comes up, the browser opens, and the user fills a form. For OpenAI, the setup endpoint writes your API key to ~/.gini/secrets.env with mode 0600; the running gateway picks it up immediately (no restart), and the autostart plist’s EnvironmentVariables is refreshed in the background so future launchd respawns retain the key. The key is never written to config.json and never leaves your machine except in API calls to OpenAI. For Codex, no token values are stored by gini — the runtime reads ~/.codex/auth.json on demand.
The terminal-driven gini setup command remains available for interactive shells that prefer it.
LaunchAgent PATH and version managers: at gini autostart enable time, the plist’s PATH is the base macOS set (bun’s dir, ~/.local/bin, /usr/local/bin, /opt/homebrew/bin, …) extended with the entries reported by your interactive $SHELL. This is what lets a launchd-supervised gateway see CLIs installed under per-user version managers — ~/.nvm/versions/node/<v>/bin, ~/.asdf/shims, ~/.volta/bin, pyenv, rbenv, etc. — the same way your terminal does. If you later switch node versions, install a new path manager, or move a CLI to a different bin dir, re-run gini autostart enable to refresh the plist.
From source (for developers):
bun install
bun run gini installUse Codex OAuth as the preferred interactive provider:
codex --login
bun run gini provider set codex gpt-5.5
bun run gini doctorGini reads Codex credentials from CODEX_AUTH_JSON or ~/.codex/auth.json and does not write token values into Gini config.
OpenAI API keys are supported as a fallback:
export OPENAI_API_KEY=...
bun run gini provider set openai gpt-5.4-mini
bun run gini doctorStart And Stop
Persistent runtime:
bun run gini start
bun run gini status
bun run gini stopForeground runtime for development:
bun run gini run --instance feature-xstart and run print the runtime gateway URL and the Next.js web URL.
The production default instance (installed via curl|bash) is pinned to memorable ports:
- web:
http://127.0.0.1:7777 - runtime:
http://127.0.0.1:7778
Developer worktree instances (auto-derived from the repo directory basename when running bun run gini) get deterministic hash-derived ports within a 100-port window starting at 7337 (runtime) / 3000 (web), so parallel worktrees coexist without manual --port wrangling. gini status prints the live URLs.
Parallel Smoke Tests
Smoke tests are isolated by default:
bun run gini smokeEach smoke run creates an ephemeral instance under /tmp, chooses available localhost ports, uses deterministic echo model providers, exercises the real runtime/API, writes evidence, and stops the runtime afterward. Multiple coding agents can run smoke tests at the same time without sharing the dev instance.
For a named persistent test instance:
bun run gini smoke --instance codex-a --state-root /tmp/gini-codex-a --log-root /tmp/gini-codex-a-logs --port 7601Verification
bun run typecheck
bun test
bun run gini smokeCommon runtime checks:
bun run gini doctor
bun run gini evidenceUpdate
To update an existing install:
gini updatePulls the latest source into ~/.gini/runtime, reinstalls dependencies, and leaves your state under ~/.gini/instances/ and the model cache at ~/.gini/models/ untouched. If a runtime is currently running, Gini restarts it automatically so the new code is picked up without a manual gini stop && gini start.
When the web app is running from the installer-managed runtime, its sidebar shows the current package/git version and exposes the same update action through its Update button.
gini update only operates on the installer-managed runtime at ~/.gini/runtime. From a repo clone, use git pull && bun install instead.
Local Development Install
If you’re working on gini-agent itself and want to test the install/update/uninstall flow against your local checkout (without pushing to GitHub):
./scripts/install.sh --localThis is the same as the default install except it clones from your local repo into ~/.gini/runtime. After you commit changes locally, gini update will pull them in. gini uninstall works exactly the same as a real install (same marker, same wrapper path).
Agent Iteration Cap
The chat-task agent loop is bounded by a per-iteration cap that prevents runaway tool-calling. The default is 90 iterations (one iteration = one model call plus any tool dispatches that follow). Most tasks finish well under 10 iterations; the cap exists as a safety bound, not a meaningful budget for normal work.
When the cap is hit the loop does NOT fail. Instead it makes one final tool-less model call asking for a summary of what was learned and what remained undone, and completes the task with that text. A warning trace records the cap hit so the activity is auditable.
To override the cap for a single instance, edit
~/.gini/instances/<instance>/config.json and add an agent object:
{
"instance": "main",
"port": 7337,
"...": "...",
"agent": {
"maxIterations": 150
}
}maxIterations must be a positive integer. Invalid values (zero, negative,
non-numeric) fall back to the built-in default and emit a warning trace on
the next task. The runtime reads config.json once at server start and
holds RuntimeConfig in memory, so edits don’t take effect until you
restart gini run (stop the tmux session and re-issue the command).
Approval Settings
Controls live behind a single endpoint (/api/settings/auto-approve):
-
approvalMode—"strict" | "auto" | "yolo". New instances default to"auto".strictgates every approval-eligible action (file_write,file_patch,terminal_exec,code_exec,browser_upload_file,send_message) for a human decision.autoauto-approves the safe set (includingsend_message) and gatesterminal_exec/code_execonly when the command matches a dangerous-pattern entry.yolobypasses every gate. See ADR approval-mode.md for the full contract. -
autoApproveCommands(shell-glob allowlist forterminal_exec). Skip the human gate for specific shell commands the agent runs. Patterns are anchored on both ends (somemo *matchesmemo notes -abut NOTrm -rf / && memo notes);*and?use standard glob semantics, everything else is a literal match. An allowlist match ALWAYS short-circuits the dangerous-pattern blocklist — explicit operator allow beats heuristic block. Auto-approved commands still write a high-riskterminal.execaudit row withevidence.autoApproved=trueandevidence.autoApprovedReason=<pattern>. -
dangerousTerminalPatterns— optional operator overlay that EXTENDS the built-in dangerous-pattern blocklist forapprovalMode: "auto". The built-ins (rm -rf /against system paths, anysudo, pipe-to-shell,chmod 777, destructive git, writes to/etc/and friends) always apply; operator entries add to them. An empty list keeps the built-ins. Only consulted in"auto"mode. -
dangerouslyAutoApprove— deprecated read alias forapprovalMode === "yolo". Returned as a derived boolean on GET; accepted on PATCH as a one-shot alias (true→"yolo",false→"auto"). New configuration should preferapprovalMode.
Read current settings:
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7337/api/settings/auto-approveSet patterns:
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
-d '{"patterns": ["memo *", "remindctl *"]}' \
http://127.0.0.1:7337/api/settings/auto-approveSwitch modes:
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
-d '{"approvalMode": "yolo"}' \
http://127.0.0.1:7337/api/settings/auto-approveAny subset of fields can be set in a single PATCH; omitted keys keep
their current value. The endpoint persists to
~/.gini/instances/<instance>/config.json in one write and takes
effect immediately for new tool dispatches.
Cleanup
Remove a single instance:
gini uninstall --instance <instance>Full uninstall (interactive, two prompts):
gini uninstallThe first prompt asks “are you sure” (default no). The second asks whether to keep instance state at ~/.gini/instances/ (default yes). The full uninstall stops every running instance, removes the installer-managed wrapper at ~/.local/bin/gini, removes the runtime checkout at ~/.gini/runtime/, and strips the PATH block (marker # Added by gini-agent installer) from your shell rc. The model cache at ~/.gini/models/ is never auto-removed — the summary prints its size and the rm -rf command to remove it manually.
Non-interactive variants:
gini uninstall --yes # full uninstall, no prompts, keep instances
gini uninstall --purge # full uninstall + delete instances (implies --yes)For disposable development and tests, override roots:
GINI_STATE_ROOT=.gini GINI_LOG_ROOT=.gini-logs bun run gini --instance sandbox smoke