sigit-code 1.5.9__tar.gz → 1.5.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/AGENTS.md +34 -5
  2. {sigit_code-1.5.9/.claude → sigit_code-1.5.11/.agents}/skills/agent-client-protocol/SKILL.md +11 -1
  3. {sigit_code-1.5.9/.agents → sigit_code-1.5.11/.claude}/skills/agent-client-protocol/SKILL.md +11 -1
  4. {sigit_code-1.5.9 → sigit_code-1.5.11}/AGENTS.md +34 -5
  5. {sigit_code-1.5.9 → sigit_code-1.5.11}/CHANGELOG.md +140 -0
  6. {sigit_code-1.5.9 → sigit_code-1.5.11}/CLAUDE.md +34 -5
  7. {sigit_code-1.5.9 → sigit_code-1.5.11}/Cargo.lock +1 -1
  8. {sigit_code-1.5.9 → sigit_code-1.5.11}/Cargo.toml +1 -1
  9. {sigit_code-1.5.9 → sigit_code-1.5.11}/PKG-INFO +5 -3
  10. {sigit_code-1.5.9 → sigit_code-1.5.11}/README.md +41 -7
  11. sigit_code-1.5.11/docs/headless.md +64 -0
  12. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/sigit/README.md +5 -3
  13. {sigit_code-1.5.9 → sigit_code-1.5.11}/nuget/sigit/README.md +10 -4
  14. {sigit_code-1.5.9 → sigit_code-1.5.11}/pypi/README.md +4 -2
  15. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/backend.rs +479 -71
  16. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/chat.rs +11 -22
  17. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/headless.rs +278 -41
  18. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/inline_tool_calls.rs +431 -79
  19. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/instructions.rs +3 -1
  20. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/main.rs +996 -171
  21. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/permissions.rs +0 -12
  22. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/provider.rs +8 -0
  23. sigit_code-1.5.11/src/session_store.rs +526 -0
  24. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/tools.rs +428 -72
  25. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/workspace.rs +14 -3
  26. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/acp_permissions.rs +261 -0
  27. sigit_code-1.5.11/tests/acp_session_list.rs +246 -0
  28. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/headless_mode.rs +166 -4
  29. sigit_code-1.5.9/src/session_store.rs +0 -261
  30. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
  31. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/branding/SKILL.md +0 -0
  32. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/run-sigit/SKILL.md +0 -0
  33. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/run-sigit/driver.mjs +0 -0
  34. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
  35. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/sigit-code-release/SKILL.md +0 -0
  36. {sigit_code-1.5.9 → sigit_code-1.5.11}/.agents/skills/tool-calling/SKILL.md +0 -0
  37. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
  38. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/branding/SKILL.md +0 -0
  39. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/run-sigit/SKILL.md +0 -0
  40. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/run-sigit/driver.mjs +0 -0
  41. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
  42. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/sigit-code-release/SKILL.md +0 -0
  43. {sigit_code-1.5.9 → sigit_code-1.5.11}/.claude/skills/tool-calling/SKILL.md +0 -0
  44. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/ci.yml +0 -0
  45. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-aur.yml +0 -0
  46. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-crates.yml +0 -0
  47. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-github.yml +0 -0
  48. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-homebrew.yml +0 -0
  49. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-npm.yml +0 -0
  50. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-nuget.yml +0 -0
  51. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-pypi.yml +0 -0
  52. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-scoop.yml +0 -0
  53. {sigit_code-1.5.9 → sigit_code-1.5.11}/.github/workflows/release-winget.yml +0 -0
  54. {sigit_code-1.5.9 → sigit_code-1.5.11}/.gitignore +0 -0
  55. {sigit_code-1.5.9 → sigit_code-1.5.11}/.nvmrc +0 -0
  56. {sigit_code-1.5.9 → sigit_code-1.5.11}/LICENSE +0 -0
  57. {sigit_code-1.5.9 → sigit_code-1.5.11}/docs/hooks.md +0 -0
  58. {sigit_code-1.5.9 → sigit_code-1.5.11}/docs/mcp.md +0 -0
  59. {sigit_code-1.5.9 → sigit_code-1.5.11}/examples/settings-with-hooks.toml +0 -0
  60. {sigit_code-1.5.9 → sigit_code-1.5.11}/examples/skills/README.md +0 -0
  61. {sigit_code-1.5.9 → sigit_code-1.5.11}/examples/skills/commit-message/SKILL.md +0 -0
  62. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/README.md.tmpl +0 -0
  63. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/package-compat.json.tmpl +0 -0
  64. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/package-main.json.tmpl +0 -0
  65. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/package.json.tmpl +0 -0
  66. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/scripts/render-main-package.cjs +0 -0
  67. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/scripts/render-platform-package.cjs +0 -0
  68. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/sigit/.gitignore +0 -0
  69. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/sigit/package.json +0 -0
  70. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/sigit/src/index.ts +0 -0
  71. {sigit_code-1.5.9 → sigit_code-1.5.11}/npm/sigit/tsconfig.json +0 -0
  72. {sigit_code-1.5.9 → sigit_code-1.5.11}/nuget/.gitignore +0 -0
  73. {sigit_code-1.5.9 → sigit_code-1.5.11}/nuget/sigit/Program.cs +0 -0
  74. {sigit_code-1.5.9 → sigit_code-1.5.11}/nuget/sigit/SiGit.Code.csproj +0 -0
  75. {sigit_code-1.5.9 → sigit_code-1.5.11}/packaging/aur/PKGBUILD.in +0 -0
  76. {sigit_code-1.5.9 → sigit_code-1.5.11}/packaging/nfpm.yaml +0 -0
  77. {sigit_code-1.5.9 → sigit_code-1.5.11}/packaging/winget/getSigit.siGitCode.installer.yaml.in +0 -0
  78. {sigit_code-1.5.9 → sigit_code-1.5.11}/packaging/winget/getSigit.siGitCode.locale.en-US.yaml.in +0 -0
  79. {sigit_code-1.5.9 → sigit_code-1.5.11}/packaging/winget/getSigit.siGitCode.yaml.in +0 -0
  80. {sigit_code-1.5.9 → sigit_code-1.5.11}/pypi/pyproject.toml +0 -0
  81. {sigit_code-1.5.9 → sigit_code-1.5.11}/pyproject.toml +0 -0
  82. {sigit_code-1.5.9 → sigit_code-1.5.11}/rust-toolchain.toml +0 -0
  83. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/account.rs +0 -0
  84. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/browser_auth.rs +0 -0
  85. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/commands.rs +0 -0
  86. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/credentials.rs +0 -0
  87. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/frontmatter.rs +0 -0
  88. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/hooks.rs +0 -0
  89. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/mcp.rs +0 -0
  90. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/models.rs +0 -0
  91. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/settings.rs +0 -0
  92. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/setup.rs +0 -0
  93. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/skills.rs +0 -0
  94. {sigit_code-1.5.9 → sigit_code-1.5.11}/src/subagents.rs +0 -0
  95. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/acp_endpoint_errors.rs +0 -0
  96. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/acp_multi_root.rs +0 -0
  97. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/acp_session_load.rs +0 -0
  98. {sigit_code-1.5.9 → sigit_code-1.5.11}/tests/acp_tool_stdin.rs +0 -0
@@ -111,9 +111,13 @@ The agent loop is backend-agnostic. The flow: a turn (messages + tool specs) goe
111
111
  feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete backend.
112
112
 
113
113
  - **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
114
- new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`
115
- (note: it bakes in smbCloud-specific context the agent should use when the repo is clearly
116
- smbCloud, and stay general otherwise).
114
+ new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
115
+ ACP session state owns its roots, conversation, and selected model even though the process has
116
+ one live backend; activating a thread parks and restores all three. Unknown session ids are
117
+ rejected instead of silently borrowing the active thread's cwd. Prompt cancellation is routed
118
+ outside `turn_lock`, which lets a client cancel the turn currently holding that lock. The
119
+ `SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
120
+ smbCloud, and stay general otherwise.
117
121
  - **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
118
122
  `ToolResult`, `TurnResult`). Two impls: `LocalBackend` (on-device via `onde::ChatEngine`) and
119
123
  `OpenAiBackend` (any OpenAI-compatible HTTP endpoint). A `BackendError` is user-facing: the
@@ -123,6 +127,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
123
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
124
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
125
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
+ well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
+ and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
+ didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
126
134
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
127
135
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
128
136
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -131,7 +139,8 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
139
  `multi_edit`, `delete_file`, `run_command`, `write_todos`, `remember`. Add a tool in both the
132
140
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
133
141
  commit attribution: when a command creates a new commit that lacks the
134
- `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
142
+ `Co-Authored-By: siGit Code` trailer (`commit_co_author_trailer()`, which reads
143
+ `siGit Code v<version>-<acp|tui|headless>` from the surface `main` sets via `set_surface`), it amends the trailer in —
135
144
  unless the commit already exists on a remote, which is never rewritten. Every child process it
136
145
  spawns (`spawn_shell`, the `git` helpers, and `hooks.rs`) sets `stdin` to null and never
137
146
  inherits it: in ACP mode sigit's stdin is the JSON-RPC pipe from the editor, so a command that
@@ -243,12 +252,32 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
252
  `handle_initialize` is what makes the rest of this reachable — without it Zed keeps the first
244
253
  root, drops the others, and shows "This agent doesn't currently support multi-root workspaces".
245
254
  The process still has one working directory, so the extra roots live in a
246
- process-global here and `project_dirs()` returns cwd-first, extras after. Project-local
255
+ process-global here and `project_dirs()` returns cwd-first, extras after.
256
+ One process also serves every thread the editor has open, so that global
257
+ (with the cwd, the backend conversation, and the background-task owner in
258
+ `tools.rs`) always belongs to the *live* session: `main.rs` keeps a
259
+ `SessionState` per session id and `activate_session` parks the live one and
260
+ installs the requested one before any prompt or config change runs. Don't
261
+ read another session's roots from the global. Project-local
247
262
  discovery reads it: skills, slash commands, subagent types, and instruction files all scan
248
263
  every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
249
264
  any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell.
250
265
  - **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
251
266
  `tokio::select!` to multiplex terminal events with streaming tokens.
267
+ - **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
268
+ clients. The legacy `-p` form reaches the same parser. Every new run gets a UUID session id;
269
+ `--resume <id>` restores that session through `session_store`, and `--output jsonl` emits a
270
+ structured session/delta/tool/result stream while logs stay on stderr. Headless permission
271
+ prompts collapse to denial unless the tool was pre-approved with `--allow-tool`.
272
+ - **`src/session_store.rs`** — durable conversations: one JSON-lines history file per session at
273
+ `$SIGIT_CONFIG_DIR/sessions/<id>.jsonl`, written atomically, restorable into either backend.
274
+ Each save also writes a `<id>.meta.json` sidecar naming the session's `cwd` and the extra roots
275
+ of a multi-root project. That sidecar is what makes a thread *listable*: ACP's `session/list`
276
+ (advertised as `sessionCapabilities.list`, handled by `handle_list_sessions` in `main.rs` — the
277
+ editor's "Import Threads" picker) must report an absolute `cwd` per session and may filter on
278
+ it, so a session without one is skipped there while still reopening by id through
279
+ `session/load`. Sidecars are written at save time, not at session start, so a thread nobody
280
+ spoke in leaves nothing behind.
252
281
  - **`src/setup.rs`** — model cache location, local model discovery, selected-model persistence.
253
282
  Must run (`setup_shared_model_cache`) *before* anything touches `ChatEngine`/`hf-hub`, since
254
283
  those read env vars once at init.
@@ -196,6 +196,7 @@ the connection and the agent anymore. Don't reintroduce the mpsc forwarder patte
196
196
  | `NewSessionRequest` | `handle_new_session` | sets cwd, resets history, advertises commands + config options |
197
197
  | `LoadSessionRequest` | `handle_load_session` | like new_session; gated by `load_session(true)` capability |
198
198
  | `ForkSessionRequest` | `handle_fork_session` | gated by `unstable_session_fork` + `SessionForkCapabilities` |
199
+ | `ListSessionsRequest` | `handle_list_sessions` | the editor's "Import Threads" picker; gated by `SessionListCapabilities` |
199
200
  | `PromptRequest` | `handle_prompt` | the turn: parse blocks → slash commands or tool-calling loop |
200
201
  | `SetSessionConfigOptionRequest` | `handle_set_session_config_option` | the Zed model picker — switches/downloads models |
201
202
  | `CancelNotification` | `handle_cancel` | notification, no response |
@@ -226,7 +227,8 @@ Ok(InitializeResponse::new(ProtocolVersion::V1) // use V1, not args.proto
226
227
  .load_session(true) // enables LoadSessionRequest
227
228
  .session_capabilities(
228
229
  SessionCapabilities::new()
229
- .fork(SessionForkCapabilities::new()), // enables ForkSessionRequest
230
+ .fork(SessionForkCapabilities::new()) // enables ForkSessionRequest
231
+ .list(SessionListCapabilities::new()), // enables ListSessionsRequest
230
232
  ),
231
233
  )
232
234
  .meta(initialize_meta())) // free-form Meta (see below)
@@ -573,6 +575,14 @@ Editor Agent
573
575
  12. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
574
576
  13. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
575
577
  carries the *rebuilt* options so the picker reflects the new current value.
578
+ 14. **`session/list` needs a `cwd` per session, which history alone can't
579
+ supply.** `SessionInfo` requires an absolute `cwd` and the request may
580
+ filter on it, so `session_store` writes a `<id>.meta.json` sidecar next to
581
+ each saved thread; sessions without one are skipped by
582
+ `handle_list_sessions` rather than guessed at. Advertising
583
+ `SessionCapabilities::new().list(SessionListCapabilities::new())` is what
584
+ turns the editor's "Import Threads" picker from an error banner into a
585
+ list.
576
586
 
577
587
  ---
578
588
 
@@ -196,6 +196,7 @@ the connection and the agent anymore. Don't reintroduce the mpsc forwarder patte
196
196
  | `NewSessionRequest` | `handle_new_session` | sets cwd, resets history, advertises commands + config options |
197
197
  | `LoadSessionRequest` | `handle_load_session` | like new_session; gated by `load_session(true)` capability |
198
198
  | `ForkSessionRequest` | `handle_fork_session` | gated by `unstable_session_fork` + `SessionForkCapabilities` |
199
+ | `ListSessionsRequest` | `handle_list_sessions` | the editor's "Import Threads" picker; gated by `SessionListCapabilities` |
199
200
  | `PromptRequest` | `handle_prompt` | the turn: parse blocks → slash commands or tool-calling loop |
200
201
  | `SetSessionConfigOptionRequest` | `handle_set_session_config_option` | the Zed model picker — switches/downloads models |
201
202
  | `CancelNotification` | `handle_cancel` | notification, no response |
@@ -226,7 +227,8 @@ Ok(InitializeResponse::new(ProtocolVersion::V1) // use V1, not args.proto
226
227
  .load_session(true) // enables LoadSessionRequest
227
228
  .session_capabilities(
228
229
  SessionCapabilities::new()
229
- .fork(SessionForkCapabilities::new()), // enables ForkSessionRequest
230
+ .fork(SessionForkCapabilities::new()) // enables ForkSessionRequest
231
+ .list(SessionListCapabilities::new()), // enables ListSessionsRequest
230
232
  ),
231
233
  )
232
234
  .meta(initialize_meta())) // free-form Meta (see below)
@@ -573,6 +575,14 @@ Editor Agent
573
575
  12. **Store `SessionId` as `SessionId`**, not `String`, so `==` is clean.
574
576
  13. **`SetSessionConfigOptionResponse::new(config_options)`** — the response
575
577
  carries the *rebuilt* options so the picker reflects the new current value.
578
+ 14. **`session/list` needs a `cwd` per session, which history alone can't
579
+ supply.** `SessionInfo` requires an absolute `cwd` and the request may
580
+ filter on it, so `session_store` writes a `<id>.meta.json` sidecar next to
581
+ each saved thread; sessions without one are skipped by
582
+ `handle_list_sessions` rather than guessed at. Advertising
583
+ `SessionCapabilities::new().list(SessionListCapabilities::new())` is what
584
+ turns the editor's "Import Threads" picker from an error banner into a
585
+ list.
576
586
 
577
587
  ---
578
588
 
@@ -111,9 +111,13 @@ The agent loop is backend-agnostic. The flow: a turn (messages + tool specs) goe
111
111
  feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete backend.
112
112
 
113
113
  - **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
114
- new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`
115
- (note: it bakes in smbCloud-specific context the agent should use when the repo is clearly
116
- smbCloud, and stay general otherwise).
114
+ new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
115
+ ACP session state owns its roots, conversation, and selected model even though the process has
116
+ one live backend; activating a thread parks and restores all three. Unknown session ids are
117
+ rejected instead of silently borrowing the active thread's cwd. Prompt cancellation is routed
118
+ outside `turn_lock`, which lets a client cancel the turn currently holding that lock. The
119
+ `SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
120
+ smbCloud, and stay general otherwise.
117
121
  - **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
118
122
  `ToolResult`, `TurnResult`). Two impls: `LocalBackend` (on-device via `onde::ChatEngine`) and
119
123
  `OpenAiBackend` (any OpenAI-compatible HTTP endpoint). A `BackendError` is user-facing: the
@@ -123,6 +127,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
123
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
124
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
125
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
+ well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
+ and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
+ didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
126
134
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
127
135
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
128
136
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -131,7 +139,8 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
139
  `multi_edit`, `delete_file`, `run_command`, `write_todos`, `remember`. Add a tool in both the
132
140
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
133
141
  commit attribution: when a command creates a new commit that lacks the
134
- `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
142
+ `Co-Authored-By: siGit Code` trailer (`commit_co_author_trailer()`, which reads
143
+ `siGit Code v<version>-<acp|tui|headless>` from the surface `main` sets via `set_surface`), it amends the trailer in —
135
144
  unless the commit already exists on a remote, which is never rewritten. Every child process it
136
145
  spawns (`spawn_shell`, the `git` helpers, and `hooks.rs`) sets `stdin` to null and never
137
146
  inherits it: in ACP mode sigit's stdin is the JSON-RPC pipe from the editor, so a command that
@@ -243,12 +252,32 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
252
  `handle_initialize` is what makes the rest of this reachable — without it Zed keeps the first
244
253
  root, drops the others, and shows "This agent doesn't currently support multi-root workspaces".
245
254
  The process still has one working directory, so the extra roots live in a
246
- process-global here and `project_dirs()` returns cwd-first, extras after. Project-local
255
+ process-global here and `project_dirs()` returns cwd-first, extras after.
256
+ One process also serves every thread the editor has open, so that global
257
+ (with the cwd, the backend conversation, and the background-task owner in
258
+ `tools.rs`) always belongs to the *live* session: `main.rs` keeps a
259
+ `SessionState` per session id and `activate_session` parks the live one and
260
+ installs the requested one before any prompt or config change runs. Don't
261
+ read another session's roots from the global. Project-local
247
262
  discovery reads it: skills, slash commands, subagent types, and instruction files all scan
248
263
  every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
249
264
  any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell.
250
265
  - **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
251
266
  `tokio::select!` to multiplex terminal events with streaming tokens.
267
+ - **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
268
+ clients. The legacy `-p` form reaches the same parser. Every new run gets a UUID session id;
269
+ `--resume <id>` restores that session through `session_store`, and `--output jsonl` emits a
270
+ structured session/delta/tool/result stream while logs stay on stderr. Headless permission
271
+ prompts collapse to denial unless the tool was pre-approved with `--allow-tool`.
272
+ - **`src/session_store.rs`** — durable conversations: one JSON-lines history file per session at
273
+ `$SIGIT_CONFIG_DIR/sessions/<id>.jsonl`, written atomically, restorable into either backend.
274
+ Each save also writes a `<id>.meta.json` sidecar naming the session's `cwd` and the extra roots
275
+ of a multi-root project. That sidecar is what makes a thread *listable*: ACP's `session/list`
276
+ (advertised as `sessionCapabilities.list`, handled by `handle_list_sessions` in `main.rs` — the
277
+ editor's "Import Threads" picker) must report an absolute `cwd` per session and may filter on
278
+ it, so a session without one is skipped there while still reopening by id through
279
+ `session/load`. Sidecars are written at save time, not at session start, so a thread nobody
280
+ spoke in leaves nothing behind.
252
281
  - **`src/setup.rs`** — model cache location, local model discovery, selected-model persistence.
253
282
  Must run (`setup_shared_model_cache`) *before* anything touches `ChatEngine`/`hf-hub`, since
254
283
  those read env vars once at init.
@@ -1,5 +1,145 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.5.11
6
+
7
+ ### Added
8
+
9
+ - **`sigit run` drives the agent headlessly with resumable sessions.** A new
10
+ non-interactive subcommand runs a prompt through the same agent loop as the
11
+ editor and terminal surfaces, for scripts, CI, and Factory clients. Every run
12
+ gets a UUID session id, writes a JSON-lines history file under
13
+ `~/.config/sigit/sessions/`, and can be restored with `--resume <id>`.
14
+ `--output jsonl` emits a structured session/delta/tool/result stream on
15
+ stdout while logs stay on stderr, and `--allow-tool` pre-approves a tool so
16
+ permission prompts collapse to denial in a non-interactive context. The
17
+ legacy `-p` form still works. Each run also rewrites the session metadata
18
+ with its own cwd and extra roots, so resuming keeps multi-root projects
19
+
20
+ - **Saved threads can be imported into the editor.** siGit Code now advertises
21
+ ACP's `sessionCapabilities.list` and answers `session/list`, so Zed's "Import
22
+ Threads" picker offers the conversations stored under
23
+ `~/.config/sigit/sessions/` instead of reporting that the agent doesn't
24
+ support the capability. A saved session gets a sidecar recording the project
25
+ directory it ran in — listing reports that `cwd`, the extra roots of a
26
+ multi-root project, an ISO 8601 last-activity timestamp, and a title taken
27
+ from the first user message — and a request may filter on `cwd` so one
28
+ project is never offered another's threads. Threads saved before this have no
29
+ sidecar and are not listed; they still reopen by id through `session/load`
30
+
31
+ ### Changed
32
+
33
+ - **The commit trailer names the siGit Code version and surface.** Commits
34
+ siGit Code makes now end with
35
+ `Co-Authored-By: siGit Code v<version>-<surface> <noreply@sigit.si>` instead
36
+ of the bare name, where the surface is `acp` (an editor), `tui` (the
37
+ terminal UI), or `headless` (`sigit run`). The model changes from session to
38
+ session; the version and surface tell you which build of the agent wrote the
39
+ commit and how it was driven. GitHub still credits the co-author, since it
40
+ matches on the address. A commit that already carries a siGit Code trailer
41
+ from an older version is left alone
42
+
43
+ - **The terminal UI's model knows which directory the project is in.** The
44
+ editor (ACP) and headless paths put the working directory in the system
45
+ prompt, but the terminal UI only added the project's instruction files, so
46
+ on `/init` a small on-device model invented a `AGENTS.md` path and failed.
47
+ All three places the terminal UI builds a prompt now go through the same
48
+ session context path the ACP sessions get. The cloud tier switch had also
49
+ been dropping the instruction files; that's fixed too
50
+
51
+ - **An empty `write_todos` list clears the plan.** `write_todos` rejected an
52
+ empty list, so the model had no way to clear a finished checklist and the
53
+ last plan stayed in the editor until the session ended. An empty list now
54
+ returns "Task list cleared." and goes to the client as an ACP plan with no
55
+ entries. The tool description tells the model it can do this
56
+
57
+ - **`sigit run` gives clearer usage errors.** A second positional prompt says
58
+ "unexpected extra argument", a bare `sigit run` says "missing prompt", and a
59
+ mistyped flag like `--quite` is rejected instead of becoming the prompt
60
+
61
+ ### Fixed
62
+
63
+ - **Each ACP session keeps its own roots, conversation, and tasks.** Zed runs
64
+ one siGit process for every open thread, but the process had a single cwd,
65
+ workspace root list, backend conversation, and background task table. The
66
+ most recent session owned all of them, so a prompt from an older thread ran
67
+ against another repo with another thread's history. A `SessionState` is now
68
+ kept per session id; before a prompt or config change runs, the live
69
+ conversation is parked and the requested session's cwd, extra roots, and
70
+ conversation are installed, rebuilding the system prompt from its roots.
71
+ `new`/`load`/`fork` share a single session-opening path, and fork carries the
72
+ source thread's conversation. Background tasks are scoped to the session that
73
+ started them, opening a session no longer wipes every other session's
74
+ permission grants, and model selection is routed per session
75
+
76
+ - **New sessions no longer carry the previous thread's history.** Opening a
77
+ new thread in Zed showed a `[Conversation summary]` from an earlier session.
78
+ The session handlers cleared the on-device engine but not the history a cloud
79
+ backend keeps for itself, and the startup routing to the cloud tier then
80
+ carried that history into the installed backend. A provider override had the
81
+ same leak. All session entry points now share a path that strips stale
82
+ history too, and unknown session ids are rejected instead of borrowing the
83
+ live thread's state
84
+
85
+ - **Unparseable tool-call markup is hidden and retried, not shown as text.**
86
+ Some models write tool calls into the reply as text. Well-formed blocks were
87
+ already recovered and run, but a block that failed to parse or never closed
88
+ was printed to the editor as-is and stayed in history, so the model later
89
+ read back a call it had "made" with no result and began writing invented
90
+ `<function_results>`. The scanner now reports those blocks as malformed; the
91
+ backend drops them from the reply and history, and when the only tool call
92
+ was malformed the model gets one retry telling it the call didn't run. A
93
+ block that merely *mentions* a tool-call marker (a backticked `<tool_call>` or an
94
+ unclosed XTML `<|open|>tools`) is now kept as prose, so a reply explaining
95
+ the scanner no longer loses everything after the first marker. The
96
+ non-streamed path now strips broken blocks beside structured calls and runs
97
+ any inline calls that did parse, matching the streaming path
98
+
99
+ - **The co-author trailer ends a conflicted merge commit.** Finishing a
100
+ conflicted merge with `git commit --no-edit` keeps git's `# Conflicts:` list
101
+ in the message, since no editor runs to strip it. When siGit Code then added
102
+ its trailer, the trailer went in above that list, so it was no longer the last
103
+ paragraph and GitHub didn't credit the co-author. The amend now drops the
104
+ leftover comment block and puts the trailer last. It also fixes a commit
105
+ that already has the trailer but still ends in that block
106
+
107
+ ## 1.5.10
108
+
109
+ ### What changed
110
+
111
+ - **Xcode's context no longer buries slash commands.** Xcode sends the user's
112
+ prompt as the last of several text blocks, with project context in the
113
+ blocks before it, so `/models` and friends sat in the middle of joined text
114
+ and were handed to the model instead of dispatching locally. Slash-command
115
+ parsing now searches the prompt's text blocks from the end, so a standalone
116
+ command in the final user block dispatches no matter what context the client
117
+ prepends
118
+ - **The nova tier is the default cloud engine.** When local inference is off
119
+ and no explicit provider override is set, the cloud tier now defaults to
120
+ `nova` (the `onde-nova` model) instead of the balanced tier, in both the
121
+ interactive and headless paths
122
+
123
+ ### Fixed
124
+
125
+ - **Malformed Chinese-model tool calls are handled end-to-end in ACP.** The
126
+ inline-call recovery that 1.5.9 added for Kimi K3's XTML protocol and GLM's
127
+ mis-tagged blocks only applied to the interactive and headless surfaces;
128
+ the ACP path never ran it, so an editor session still surfaced raw protocol
129
+ text and dropped the calls. Recovery now runs in the ACP prompt loop too,
130
+ covered by integration tests, and the tool names are checked against the
131
+ turn's offered tools before anything executes
132
+ - **Suppressed tool-call log spam is gone.** The guard that logs when a
133
+ structured call is dropped for arriving as forced text fired once per
134
+ streamed argument fragment — one malformed call could log a warning per
135
+ chunk. The check now fires once per turn with the count and names of what
136
+ was dropped, and the non-streaming path reports the same
137
+ - **A history-replay test no longer races on the process cwd.** One ACP test
138
+ built its expected path from `std::env::current_dir()` while other tests
139
+ briefly swap that process-global cwd, so on CI it could read a temp
140
+ directory mid-swap and compare a truncated title against an untruncated
141
+ expectation. It now uses a fixed path it never needed to derive
142
+
3
143
  ## 1.5.9
4
144
 
5
145
  ### Fixed
@@ -111,9 +111,13 @@ The agent loop is backend-agnostic. The flow: a turn (messages + tool specs) goe
111
111
  feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete backend.
112
112
 
113
113
  - **`src/main.rs`** — entry point, mode dispatch, the full ACP `Agent` impl (session lifecycle:
114
- new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`
115
- (note: it bakes in smbCloud-specific context the agent should use when the repo is clearly
116
- smbCloud, and stay general otherwise).
114
+ new/load/fork/prompt/cancel, config options, slash-command advertisement), and the `SYSTEM_PROMPT`.
115
+ ACP session state owns its roots, conversation, and selected model even though the process has
116
+ one live backend; activating a thread parks and restores all three. Unknown session ids are
117
+ rejected instead of silently borrowing the active thread's cwd. Prompt cancellation is routed
118
+ outside `turn_lock`, which lets a client cancel the turn currently holding that lock. The
119
+ `SYSTEM_PROMPT` bakes in smbCloud-specific context the agent should use when the repo is clearly
120
+ smbCloud, and stay general otherwise.
117
121
  - **`src/backend.rs`** — the `InferenceBackend` trait and neutral types (`ToolSpec`, `ToolCall`,
118
122
  `ToolResult`, `TurnResult`). Two impls: `LocalBackend` (on-device via `onde::ChatEngine`) and
119
123
  `OpenAiBackend` (any OpenAI-compatible HTTP endpoint). A `BackendError` is user-facing: the
@@ -123,6 +127,10 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
123
127
  endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
124
128
  the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
125
129
  explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
130
+ Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
131
+ well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
132
+ and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
133
+ didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
126
134
  - **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
127
135
  wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
128
136
  `~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
@@ -131,7 +139,8 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
131
139
  `multi_edit`, `delete_file`, `run_command`, `write_todos`, `remember`. Add a tool in both the
132
140
  spec list (`all_tools`) and the execute `match` (`execute_tool`). `run_command` also enforces
133
141
  commit attribution: when a command creates a new commit that lacks the
134
- `Co-Authored-By: siGit Code` trailer (`COMMIT_CO_AUTHOR_TRAILER`), it amends the trailer in —
142
+ `Co-Authored-By: siGit Code` trailer (`commit_co_author_trailer()`, which reads
143
+ `siGit Code v<version>-<acp|tui|headless>` from the surface `main` sets via `set_surface`), it amends the trailer in —
135
144
  unless the commit already exists on a remote, which is never rewritten. Every child process it
136
145
  spawns (`spawn_shell`, the `git` helpers, and `hooks.rs`) sets `stdin` to null and never
137
146
  inherits it: in ACP mode sigit's stdin is the JSON-RPC pipe from the editor, so a command that
@@ -243,12 +252,32 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
243
252
  `handle_initialize` is what makes the rest of this reachable — without it Zed keeps the first
244
253
  root, drops the others, and shows "This agent doesn't currently support multi-root workspaces".
245
254
  The process still has one working directory, so the extra roots live in a
246
- process-global here and `project_dirs()` returns cwd-first, extras after. Project-local
255
+ process-global here and `project_dirs()` returns cwd-first, extras after.
256
+ One process also serves every thread the editor has open, so that global
257
+ (with the cwd, the backend conversation, and the background-task owner in
258
+ `tools.rs`) always belongs to the *live* session: `main.rs` keeps a
259
+ `SessionState` per session id and `activate_session` parks the live one and
260
+ installs the requested one before any prompt or config change runs. Don't
261
+ read another session's roots from the global. Project-local
247
262
  discovery reads it: skills, slash commands, subagent types, and instruction files all scan
248
263
  every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
249
264
  any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell.
250
265
  - **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
251
266
  `tokio::select!` to multiplex terminal events with streaming tokens.
267
+ - **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
268
+ clients. The legacy `-p` form reaches the same parser. Every new run gets a UUID session id;
269
+ `--resume <id>` restores that session through `session_store`, and `--output jsonl` emits a
270
+ structured session/delta/tool/result stream while logs stay on stderr. Headless permission
271
+ prompts collapse to denial unless the tool was pre-approved with `--allow-tool`.
272
+ - **`src/session_store.rs`** — durable conversations: one JSON-lines history file per session at
273
+ `$SIGIT_CONFIG_DIR/sessions/<id>.jsonl`, written atomically, restorable into either backend.
274
+ Each save also writes a `<id>.meta.json` sidecar naming the session's `cwd` and the extra roots
275
+ of a multi-root project. That sidecar is what makes a thread *listable*: ACP's `session/list`
276
+ (advertised as `sessionCapabilities.list`, handled by `handle_list_sessions` in `main.rs` — the
277
+ editor's "Import Threads" picker) must report an absolute `cwd` per session and may filter on
278
+ it, so a session without one is skipped there while still reopening by id through
279
+ `session/load`. Sidecars are written at save time, not at session start, so a thread nobody
280
+ spoke in leaves nothing behind.
252
281
  - **`src/setup.rs`** — model cache location, local model discovery, selected-model persistence.
253
282
  Must run (`setup_shared_model_cache`) *before* anything touches `ChatEngine`/`hf-hub`, since
254
283
  those read env vars once at init.
@@ -5778,7 +5778,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
5778
5778
 
5779
5779
  [[package]]
5780
5780
  name = "sigit"
5781
- version = "1.5.9"
5781
+ version = "1.5.11"
5782
5782
  dependencies = [
5783
5783
  "agent-client-protocol",
5784
5784
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sigit"
3
- version = "1.5.9"
3
+ version = "1.5.11"
4
4
  edition = "2024"
5
5
  description = "siGit Code — ACP-compatible AI coding agent. Sí, git."
6
6
  documentation = "https://github.com/getsigit/sigit"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sigit-code
3
- Version: 1.5.9
3
+ Version: 1.5.11
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -57,7 +57,8 @@ This installs a native `sigit` binary for your platform. You do not need a compi
57
57
  sigit
58
58
  ```
59
59
 
60
- That opens the local chat UI.
60
+ That opens the local chat UI. Editors drive the same binary in ACP mode, which
61
+ needs the `--acp` argument.
61
62
 
62
63
  ### Zed
63
64
 
@@ -68,7 +69,8 @@ siGit Code works as an [ACP-compatible](https://github.com/nicobailon/agent-clie
68
69
  "agent_servers": {
69
70
  "siGit Code": {
70
71
  "type": "custom",
71
- "command": "/absolute/path/to/sigit"
72
+ "command": "/absolute/path/to/sigit",
73
+ "args": ["--acp"]
72
74
  }
73
75
  }
74
76
  }
@@ -8,7 +8,8 @@
8
8
  <a href="https://github.com/getsigit/sigit/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
9
9
  </p>
10
10
 
11
- siGit Code is a local coding agent. It runs on your machine, not someone else's. No API keys, no cloud round-trips, no subscription.
11
+ siGit Code is the local agent runtime for **siGit Factory**. It runs on your machine by default,
12
+ with optional cloud inference and remote automation when you choose them.
12
13
 
13
14
  Its home is [code.sigit.si](https://code.sigit.si). You can run it yourself, as below, or use the hosted version (siGit Code Cloud) there if you would rather not run a model locally. [sigit.si](https://sigit.si) is Git hosting built for AI workflows.
14
15
 
@@ -16,9 +17,13 @@ It works in any codebase. In smbCloud repos it is more useful out of the box bec
16
17
 
17
18
  You can use it in two ways:
18
19
 
19
- - **ACP mode:** Zed or another ACP-compatible editor starts it over stdio
20
+ - **ACP mode:** Zed or another ACP-compatible editor starts it over stdio with the `--acp` argument
20
21
  - **Terminal mode:** run `sigit` for the interactive chat UI
21
22
 
23
+ Every ACP client must pass `--acp`. It takes no other arguments, and it is what
24
+ tells the binary to speak the Agent Client Protocol instead of opening the
25
+ terminal UI.
26
+
22
27
  | Platform | ACP mode | Terminal mode |
23
28
  |----------|----------|---------------|
24
29
  | macOS | ✓ | ✓ |
@@ -59,7 +64,8 @@ Add this to `~/.config/zed/settings.json`:
59
64
  "agent_servers": {
60
65
  "siGit Code": {
61
66
  "type": "custom",
62
- "command": "/absolute/path/to/sigit"
67
+ "command": "/absolute/path/to/sigit",
68
+ "args": ["--acp"]
63
69
  }
64
70
  }
65
71
  }
@@ -74,8 +80,8 @@ In Xcode, open **Settings > Intelligence > Agents**, add a custom agent, and set
74
80
  - **Executable:** the absolute path to `sigit`
75
81
  - **Arguments:** `--acp`
76
82
 
77
- The explicit `--acp` mode is designed for Xcode: it loads the selected on-device
78
- model on the first prompt, so you do not need to send `/load` from the Xcode chat.
83
+ In Xcode the `--acp` mode also loads the selected on-device model on the first
84
+ prompt, so you do not need to send `/load` from the Xcode chat.
79
85
 
80
86
  To let siGit use Xcode's build, test, and project tools, enable **Allow external
81
87
  agents to use Xcode tools** in Xcode's Intelligence settings, keep the project
@@ -100,7 +106,7 @@ Install from the [Visual Studio Code Marketplace](https://marketplace.visualstud
100
106
  "sigit": {
101
107
  "name": "siGit (on-device)",
102
108
  "command": "sigit",
103
- "args": [],
109
+ "args": ["--acp"],
104
110
  "env": {}
105
111
  },
106
112
  },
@@ -117,7 +123,7 @@ Install [ACP Client](https://marketplace.visualstudio.com/items?itemName=formula
117
123
  "acp.agents": {
118
124
  "siGit Code": {
119
125
  "command": "sigit",
120
- "args": [],
126
+ "args": ["--acp"],
121
127
  "env": {}
122
128
  }
123
129
  }
@@ -130,6 +136,34 @@ Run `sigit` in a terminal and you get the same model and system prompt as the ed
130
136
 
131
137
  Terminal mode currently needs Unix terminal behavior, so it works on macOS and Linux only.
132
138
 
139
+ ## Headless and CI mode
140
+
141
+ Use `sigit run` to execute the same agent from scripts, CI, or another siGit Factory client:
142
+
143
+ ```sh
144
+ sigit run "Review this repository and run the focused tests" --cwd .
145
+ ```
146
+
147
+ Every run receives a durable session ID. The ID is printed to stderr in text mode and included
148
+ in every event in JSONL mode. Resume the same conversation from a later command—or import it in
149
+ an ACP client—by passing that ID:
150
+
151
+ ```sh
152
+ sigit run "Continue with the next issue" --resume <session-id>
153
+ ```
154
+
155
+ For automation, JSONL output provides session, assistant-delta, tool-call, tool-result, result,
156
+ and error events on stdout. Logs remain on stderr.
157
+
158
+ ```sh
159
+ sigit run "Run the checks" --output jsonl --allow-tool run_command
160
+ ```
161
+
162
+ Mutating tools still follow siGit's permission policy. Because a headless run cannot answer an
163
+ interactive permission prompt, approve only the tools the run needs with repeatable
164
+ `--allow-tool` flags. The legacy `sigit -p "<prompt>"` form remains supported. See the
165
+ [headless execution reference](docs/headless.md) for the event contract and exit codes.
166
+
133
167
  ## Platform support
134
168
 
135
169
  | Platform | Architecture |
@@ -0,0 +1,64 @@
1
+ # Headless execution
2
+
3
+ `sigit run` exposes the siGit Code agent runtime to scripts, CI, and siGit Factory clients.
4
+ It uses the same provider selection, project instructions, skills, tools, MCP servers, and
5
+ permission policy as the interactive and ACP surfaces.
6
+
7
+ ## Run and resume
8
+
9
+ ```sh
10
+ sigit run "Review this repository" --cwd /path/to/repository
11
+ ```
12
+
13
+ A new run receives a UUID session ID and saves its conversation under the siGit configuration
14
+ directory. Text mode prints the ID to stderr as `Session: <id>`. Resume it later with:
15
+
16
+ ```sh
17
+ sigit run "Continue with the fixes" --resume <id>
18
+ ```
19
+
20
+ The saved metadata includes the primary working directory, additional project roots, and model.
21
+ This makes the same session discoverable through ACP `session/list` and loadable by an editor.
22
+ Each run rewrites that metadata with its own `--cwd` and `--add-dir` values, so a resume records
23
+ where the session last ran. Pass the same `--add-dir` flags again to keep extra roots attached.
24
+
25
+ Use repeatable `--add-dir <path>` flags for multi-root workspaces. Mutating tools that would ask
26
+ for interactive permission are denied in headless mode unless granted with
27
+ `--allow-tool <name>`. A repeatable `--deny-tool <name>` takes precedence over grants and
28
+ settings.
29
+
30
+ ## JSONL output
31
+
32
+ Pass `--output jsonl` for a machine-readable stdout stream:
33
+
34
+ ```sh
35
+ sigit run "Run the focused tests" --output jsonl --allow-tool run_command
36
+ ```
37
+
38
+ Each line is one JSON object. Every event includes `type` and `session_id`. A run that gets as far
39
+ as inference starts with a `session` event. A run that fails before that, because no remote
40
+ provider is configured or the `--resume` session doesn't exist, emits a single `error` event and
41
+ nothing else.
42
+
43
+ | Type | Additional fields | Meaning |
44
+ |---|---|---|
45
+ | `session` | `resumed` | Identifies the run before inference starts. |
46
+ | `assistant_delta` | `text` | One visible streamed response fragment. |
47
+ | `tool_call` | `tool_call_id`, `name`, `arguments` | A tool requested by the model. `arguments` is the tool's JSON string. |
48
+ | `tool_result` | `tool_call_id`, `name`, `content` | The executed result or permission denial returned to the model. |
49
+ | `result` | `text`, `tool_rounds` | The completed turn and its final visible text. |
50
+ | `error` | `message` | A provider, session, or inference failure. |
51
+
52
+ Logs and diagnostics stay on stderr. `--quiet` applies only to text output and cannot be combined
53
+ with JSONL output.
54
+
55
+ ## Exit codes
56
+
57
+ | Code | Meaning |
58
+ |---|---|
59
+ | `0` | The turn completed, including turns where a tool was denied by policy. |
60
+ | `1` | Provider resolution, session restoration, inference, or tool-loop failure. |
61
+ | `2` | Invalid command-line arguments or working directories. |
62
+
63
+ The earlier `sigit -p "<prompt>"` syntax remains an alias for headless execution and accepts the
64
+ same flags.