@elie-laloum/outpost 1.1.0

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 (128) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.fr.md +251 -0
  4. package/README.md +226 -0
  5. package/ROADMAP.md +34 -0
  6. package/SECURITY.md +15 -0
  7. package/dist/application/campaign.d.ts +33 -0
  8. package/dist/application/campaign.js +195 -0
  9. package/dist/application/campaign.js.map +1 -0
  10. package/dist/application/execution.d.ts +48 -0
  11. package/dist/application/execution.js +322 -0
  12. package/dist/application/execution.js.map +1 -0
  13. package/dist/application/interactive-brief.d.ts +3 -0
  14. package/dist/application/interactive-brief.js +35 -0
  15. package/dist/application/interactive-brief.js.map +1 -0
  16. package/dist/application/outpost.d.ts +91 -0
  17. package/dist/application/outpost.js +629 -0
  18. package/dist/application/outpost.js.map +1 -0
  19. package/dist/application/remote-workspace.d.ts +11 -0
  20. package/dist/application/remote-workspace.js +264 -0
  21. package/dist/application/remote-workspace.js.map +1 -0
  22. package/dist/application/tasks.d.ts +17 -0
  23. package/dist/application/tasks.js +36 -0
  24. package/dist/application/tasks.js.map +1 -0
  25. package/dist/cli/main.d.ts +2 -0
  26. package/dist/cli/main.js +94 -0
  27. package/dist/cli/main.js.map +1 -0
  28. package/dist/cli/scaffold.d.ts +28 -0
  29. package/dist/cli/scaffold.js +169 -0
  30. package/dist/cli/scaffold.js.map +1 -0
  31. package/dist/cli/starters.d.ts +3 -0
  32. package/dist/cli/starters.js +34 -0
  33. package/dist/cli/starters.js.map +1 -0
  34. package/dist/domain/backlog.d.ts +16 -0
  35. package/dist/domain/backlog.js +23 -0
  36. package/dist/domain/backlog.js.map +1 -0
  37. package/dist/domain/errors.d.ts +11 -0
  38. package/dist/domain/errors.js +37 -0
  39. package/dist/domain/errors.js.map +1 -0
  40. package/dist/domain/ports.d.ts +183 -0
  41. package/dist/domain/ports.js +2 -0
  42. package/dist/domain/ports.js.map +1 -0
  43. package/dist/domain/prompts.d.ts +22 -0
  44. package/dist/domain/prompts.js +45 -0
  45. package/dist/domain/prompts.js.map +1 -0
  46. package/dist/domain/response.d.ts +37 -0
  47. package/dist/domain/response.js +50 -0
  48. package/dist/domain/response.js.map +1 -0
  49. package/dist/domain/workflow.d.ts +68 -0
  50. package/dist/domain/workflow.js +246 -0
  51. package/dist/domain/workflow.js.map +1 -0
  52. package/dist/index.d.ts +27 -0
  53. package/dist/index.js +13 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/infrastructure/abort.d.ts +1 -0
  56. package/dist/infrastructure/abort.js +16 -0
  57. package/dist/infrastructure/abort.js.map +1 -0
  58. package/dist/infrastructure/conversations.d.ts +30 -0
  59. package/dist/infrastructure/conversations.js +222 -0
  60. package/dist/infrastructure/conversations.js.map +1 -0
  61. package/dist/infrastructure/files.d.ts +5 -0
  62. package/dist/infrastructure/files.js +85 -0
  63. package/dist/infrastructure/files.js.map +1 -0
  64. package/dist/infrastructure/git.d.ts +14 -0
  65. package/dist/infrastructure/git.js +304 -0
  66. package/dist/infrastructure/git.js.map +1 -0
  67. package/dist/infrastructure/journal.d.ts +10 -0
  68. package/dist/infrastructure/journal.js +53 -0
  69. package/dist/infrastructure/journal.js.map +1 -0
  70. package/dist/infrastructure/process.d.ts +6 -0
  71. package/dist/infrastructure/process.js +133 -0
  72. package/dist/infrastructure/process.js.map +1 -0
  73. package/dist/infrastructure/reporter.d.ts +10 -0
  74. package/dist/infrastructure/reporter.js +47 -0
  75. package/dist/infrastructure/reporter.js.map +1 -0
  76. package/dist/infrastructure/settings.d.ts +3 -0
  77. package/dist/infrastructure/settings.js +41 -0
  78. package/dist/infrastructure/settings.js.map +1 -0
  79. package/dist/infrastructure/shutdown.d.ts +1 -0
  80. package/dist/infrastructure/shutdown.js +67 -0
  81. package/dist/infrastructure/shutdown.js.map +1 -0
  82. package/dist/infrastructure/terminal.d.ts +11 -0
  83. package/dist/infrastructure/terminal.js +19 -0
  84. package/dist/infrastructure/terminal.js.map +1 -0
  85. package/dist/infrastructure/transfer.d.ts +3 -0
  86. package/dist/infrastructure/transfer.js +29 -0
  87. package/dist/infrastructure/transfer.js.map +1 -0
  88. package/dist/providers/agents.d.ts +17 -0
  89. package/dist/providers/agents.js +204 -0
  90. package/dist/providers/agents.js.map +1 -0
  91. package/dist/providers/backlogs.d.ts +9 -0
  92. package/dist/providers/backlogs.js +82 -0
  93. package/dist/providers/backlogs.js.map +1 -0
  94. package/dist/providers/cloud-files.d.ts +4 -0
  95. package/dist/providers/cloud-files.js +66 -0
  96. package/dist/providers/cloud-files.js.map +1 -0
  97. package/dist/providers/container.d.ts +21 -0
  98. package/dist/providers/container.js +310 -0
  99. package/dist/providers/container.js.map +1 -0
  100. package/dist/providers/daytona.d.ts +10 -0
  101. package/dist/providers/daytona.js +153 -0
  102. package/dist/providers/daytona.js.map +1 -0
  103. package/dist/providers/docker.d.ts +3 -0
  104. package/dist/providers/docker.js +3 -0
  105. package/dist/providers/docker.js.map +1 -0
  106. package/dist/providers/factories.d.ts +9 -0
  107. package/dist/providers/factories.js +12 -0
  108. package/dist/providers/factories.js.map +1 -0
  109. package/dist/providers/local.d.ts +4 -0
  110. package/dist/providers/local.js +64 -0
  111. package/dist/providers/local.js.map +1 -0
  112. package/dist/providers/podman.d.ts +3 -0
  113. package/dist/providers/podman.js +3 -0
  114. package/dist/providers/podman.js.map +1 -0
  115. package/dist/providers/vercel.d.ts +9 -0
  116. package/dist/providers/vercel.js +158 -0
  117. package/dist/providers/vercel.js.map +1 -0
  118. package/dist/providers/versions.d.ts +4 -0
  119. package/dist/providers/versions.js +5 -0
  120. package/dist/providers/versions.js.map +1 -0
  121. package/docs/api.md +147 -0
  122. package/docs/architecture.md +21 -0
  123. package/docs/migration-1.1.fr.md +44 -0
  124. package/docs/migration-1.1.md +38 -0
  125. package/docs/operations.md +101 -0
  126. package/docs/providers.md +119 -0
  127. package/docs/workflows.md +101 -0
  128. package/package.json +95 -0
package/docs/api.md ADDED
@@ -0,0 +1,147 @@
1
+ # API reference
2
+
3
+ ## Domain and ownership
4
+
5
+ `Workspace` owns a Git checkout and its lock. `Sandbox` owns a running environment attached to exactly one workspace. `dispatch` is one agent job, potentially containing several turns. `Task<T>` is one typed workflow node. A native conversation belongs to an agent and can outlive both workspace and sandbox.
6
+
7
+ | Entry point | Lifetime |
8
+ | ------------------------ | -------------------------------------------------------------------------------------- |
9
+ | `dispatch(options)` | Creates and closes its own sandbox; closes its workspace unless supplied. |
10
+ | `attach(options)` | Opens a terminal session, synchronizes changes and closes owned resources. |
11
+ | `createSandbox(options)` | Returns a warm handle with `dispatch`, `resume`, `fork`, `attach`, `command`, `close`. |
12
+ | `openWorkspace(options)` | Returns a workspace with `dispatch`, `sandbox`, `attach`, `integrate`, `close`. |
13
+
14
+ Sandbox and workspace handles implement `Symbol.asyncDispose` for `await using`. Explicit `close()` is supported and idempotent. Close the sandbox before closing a separately owned workspace. A warm sandbox rejects overlapping operations. Separate workspaces hold separate locks; automatic merges hold a host-branch merge lock.
15
+
16
+ ## WorkspaceOptions
17
+
18
+ | Field | Type / default | Meaning |
19
+ | ------------------ | ------------------------------------- | ---------------------------------------------------------- |
20
+ | `repository` | `string`, current directory | Repository root or a directory inside it. |
21
+ | `branch` | `BranchPolicy`, `{ mode: "current" }` | Checkout, named worktree, or temporary integration branch. |
22
+ | `copies` | `readonly string[]` | Repository-relative inputs copied before workspace hooks. |
23
+ | `limits.copyMs` | `number`, 60000 | Copy deadline. |
24
+ | `limits.gitMs` | `number`, 30000 | Git setup operation deadline. |
25
+ | `limits.collectMs` | `number`, 30000 | Commit collection deadline. |
26
+ | `limits.mergeMs` | `number`, 30000 | Host merge deadline. |
27
+
28
+ Branch variants:
29
+
30
+ ```ts
31
+ type BranchPolicy =
32
+ | { mode: "current" }
33
+ | { mode: "named"; name: string; from?: string }
34
+ | { mode: "integrate"; from?: string };
35
+ ```
36
+
37
+ `from` accepts a Git commit-ish and is resolved before branch creation. An existing named branch is reused. Managed worktrees live under `.outpost/workspaces`. A branch checked out outside its managed location is not moved. Integration requires an attached host branch and never switches the host checkout. Dirty worktrees survive close; `close({ preserve: true })` also retains clean ones. Inspect the returned `retainedDirectory`.
38
+
39
+ ## SandboxOptions
40
+
41
+ Includes `WorkspaceOptions`, plus:
42
+
43
+ | Field | Meaning |
44
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `agent` | Default `AgentAdapter` for warm operations; optional on `createSandbox`, required on one-shot `dispatch`/`attach`. Use `codex()` or `claude()`. |
46
+ | `provider` | `SandboxProvider`; defaults to `docker()`. |
47
+ | `workspace` | Existing workspace. Cannot be combined with `repository`, `branch` or `copies`. |
48
+ | `hooks` | `workspaceReady`, `hostReady`, `sandboxReady` command arrays. |
49
+ | `signal` | Cancels provisioning. In one-shot calls, also cancels execution. |
50
+ | `logging` | `false`, `"stdout"`, or `{ file?, verbose? }`; default generated JSONL file. |
51
+ | `bootstrap` | Remote agent installation when absent; defaults to `true`. |
52
+ | `conversationHome` | Host directory containing `.claude`/`.codex`; defaults to OS home. |
53
+
54
+ Hook order is copies → `workspaceReady` on host → provider acquisition/synchronization → concurrent `hostReady` and `sandboxReady`. Host commands run sequentially. Sandbox commands run concurrently. Failure aborts sibling hooks and waits for their cleanup. `openWorkspace({ hooks })` runs `workspaceReady` immediately and passes the remaining hooks to later sandboxes without repeating it. Commands may choose a directory, environment and timeout. Sandbox commands can request `elevated: true` on providers supporting elevation. Host execution does not elevate privileges.
55
+
56
+ ## DispatchOptions
57
+
58
+ | Field | Default / meaning |
59
+ | -------------- | --------------------------------------------------------------------------------------------- |
60
+ | `brief` | Required `{ text }` or `{ file, values? }`. |
61
+ | `agent` | Overrides the warm sandbox's default adapter for this job, including its model and variables. |
62
+ | `logging` | Overrides the sandbox's logging policy for this job. |
63
+ | `label` | Optional label included in journal records and generated log filenames. |
64
+ | `passes` | Positive integer, default 1. Maximum agent turns when no response validator is supplied. |
65
+ | `until` | String or strings; default `<outpost>done</outpost>`. Empty array disables markers. |
66
+ | `idleMs` | 600000; maximum silence before completion. Any output refreshes it. |
67
+ | `settleMs` | 60000; grace period after completion, refreshed by output. |
68
+ | `deadlineMs` | 3600000; hard deadline for each agent command. |
69
+ | `expansionMs` | 30000; deadline for each embedded prompt command. |
70
+ | `signal` | Cancels this operation, preserving a warm sandbox. |
71
+ | `continuation` | `{ id, fork? }`; native resume or conversation fork. |
72
+ | `response` | A `ResponseSpec<T>` produced by `response.text/json`. |
73
+ | `observe` | Receives normalized events; exceptions cannot fail the job. |
74
+ | `warn` | Receives nonfatal warnings; exceptions cannot fail the job. |
75
+
76
+ Resume and structured output require `passes: 1`. A one-shot continuation validates the host transcript before allocating an environment. Warm continuations reuse sessions already present, otherwise importing the host transcript.
77
+
78
+ `DispatchResult<T>` contains `text`, `value`, `turns`, `usage`, `completed`, optional `completion`, `conversation`, `transcript`, `log`, `branch`, `directory`, `commits`, optional `retainedDirectory`, and `resume`/`fork` methods. `value` is inferred from the response validator. Without a validator it is `undefined`. Usage contains raw `input`, `cached` (cache read), optional `cacheCreated` (cache creation) and `output` token counts. Claude transcript usage takes the final assistant message, independently from streamed totals. Costs are not estimated.
79
+
80
+ Each turn contains `text`, `status`, `durationMs`, `usage`, and optional `conversation` and `transcript`. Commits contain their original `oid` and `subject`. A budget exhausted without a completion marker returns `completed: false`; process failures throw.
81
+
82
+ ## Agent settings
83
+
84
+ ```ts
85
+ codex({
86
+ model: "your-model",
87
+ reasoning: "high",
88
+ approvalReviewer: "auto_review",
89
+ saveConversations: true,
90
+ variables: {},
91
+ });
92
+
93
+ claude({
94
+ model: "sonnet",
95
+ reasoning: "high",
96
+ permissions: "acceptEdits",
97
+ saveConversations: true,
98
+ variables: {},
99
+ });
100
+ ```
101
+
102
+ Model is optional; the installed CLI chooses its default. Codex reasoning accepts `low`, `medium`, `high`, `xhigh`. Claude also accepts `max`. Actual model support is determined by the CLI/provider. Claude permission modes are `default`, `acceptEdits`, `plan`, `auto`, `dontAsk`, `bypassPermissions`.
103
+
104
+ Noninteractive defaults avoid permission prompts because the outer sandbox is responsible for isolation. Codex can instead use the auto-review approval reviewer. Interactive commands use native terminal behavior and native session resume/fork commands.
105
+
106
+ Events are a discriminated union with `kind`: `phase`, `prompt`, `text`, `result`, `tool`, `conversation`, `usage`, `summary`, `warning`, `failure`, `finished`, `raw`. Every public observation adds a one-based `pass` and ISO timestamp `at`. Protocol noise is retained as `raw`. Adapters implement `request(input): Command` and `events(line): AgentEvent[]`; a custom adapter can omit native conversation support.
107
+
108
+ ## Command and attachment
109
+
110
+ ```ts
111
+ const result = await sandbox.command({
112
+ executable: "npm",
113
+ arguments: ["test"],
114
+ directory: "/workspace",
115
+ variables: { CI: "true" },
116
+ deadlineMs: 120_000,
117
+ retain: 65_536,
118
+ observe(channel, text) {
119
+ process.stdout.write(text);
120
+ },
121
+ });
122
+ ```
123
+
124
+ Arguments are passed directly, without implicit shell expansion. For shell syntax, explicitly invoke `sh -c` (inside Linux sandboxes) or the host shell. Default directory is the sandbox workspace. `stdin` supplies a string. `interactive` inherits terminal input/output by default; `terminal: { input?, output?, error? }` supplies caller-owned streams. Raw mode and cursor visibility are restored after attachment. `retain` bounds the captured tail of each stream; observers receive all output. `CommandResult` contains `status`, `stdout`, `stderr`.
125
+
126
+ `attach({ agent?, brief?, continuation?, signal?, ask?, terminal? })` uses the adapter's terminal mode. Missing file-prompt variables are requested once per variable through the TTY or the optional async `ask(name)` callback. Existing values are preserved. Docker, Podman and host support attachment. Remote cloud adapters reject it explicitly. Both warm and one-shot results include commits, branch and workspace directory. The top-level result additionally returns disposal information.
127
+
128
+ ## Errors and recovery
129
+
130
+ `OutpostError` has a machine-readable `code`, frozen `details`, `recovery`, and optional `cause`. `recoveryDetails(error)` retrieves recovery metadata for native errors and cancellation reasons as well, preserving their original identity. Codes: `configuration`, `process`, `timeout`, `aborted`, `workspace`, `conflict`, `prompt`, `response`, `session`, `provider`. Multiple failures can surface as `AggregateError` without losing the original causes.
131
+
132
+ `ResponseError` adds `tag`, optional `raw` and `recovery`. Recovery records include the conversation and workspace; after collection they also include commits, transcript and log. Native main-transcript capture failure fails the dispatch. Child transcript capture errors only warn.
133
+
134
+ Remote recovery directories contain Git bundles, binary patches and copied untracked files. A failed synchronization reports its recovery path. Do not delete it before inspecting the failure. See [Operations](operations.md).
135
+
136
+ ## Additional 1.1 contracts
137
+
138
+ - `WorkspaceOptions.label` names generated branches and workspace directories; `hooks` and `signal` are available on standalone workspaces.
139
+ - `SandboxOptions.includeUncommitted` explicitly seeds remote patches and untracked files; the default is committed history only.
140
+ - `DispatchOptions.idleWarningMs` defaults to 60000; `diagnostic(message)` receives expansion-size estimates. Relative file briefs use the caller's cwd.
141
+ - `AgentAdapter.resumable` declares repair capability. `storage?: ConversationStore` supplies locate/capture/restore; `transcriptUsage?(text)` extracts authoritative usage.
142
+ - `conversations.native(format)` returns a store. Helpers include `locate`, `capture`, `restore`, `rewrite`, `projectKey`, `claudePath`, `directory` and `destination`.
143
+ - Cold `DispatchResult.resume/fork` accepts `ContinuationOptions`, including a new branch, provider and lifecycle hooks. `WarmDispatchResult` accepts dispatch settings only.
144
+ - `TransferOptions` contains optional `signal` and `deadlineMs`. `SandboxLease.upload/download` accept it as a third argument. Orchestration uses `limits.copyMs`, with a 120000 ms transfer default.
145
+ - `reporter` renders observations with label, verbose, quiet and custom writer settings.
146
+ - `campaign`, `CampaignOptions`, `CampaignResult`, `CampaignEvent`, `IssueOutcome`, `Issue`, `Assignment` and `Backlog` define issue delivery. See the [workflow guide](workflows.md#issue-campaigns).
147
+ - `githubBacklog` and `beadsBacklog` accept `directory`, `label` and `deadlineMs`. Their commands receive declared project environment values.
@@ -0,0 +1,21 @@
1
+ # Architecture
2
+
3
+ Outpost separates domain contracts from orchestration and infrastructure.
4
+
5
+ | Layer | Responsibility |
6
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
7
+ | `src/domain` | Workspace policy, command/agent/provider ports, prompts, responses, workflow graph, issue/backlog contracts and errors. |
8
+ | `src/application` | Resource ownership, dispatch coordination, workflow task helpers, issue campaigns and remote synchronization. |
9
+ | `src/infrastructure` | Git, filesystem, native transcripts, process supervision, journals and shutdown cleanup. |
10
+ | `src/providers` | Claude/Codex protocols and Docker/Podman/host/Vercel/Daytona backends. |
11
+ | `src/cli` | Onboarding, starter templates and image lifecycle. |
12
+
13
+ The main boundaries are `AgentAdapter`, `ConversationStore`, `SandboxProvider`, `SandboxLease` and `Backlog`. An agent converts typed input into a command and normalizes protocol events. A provider owns allocation and release. The application owns synchronization and recovery. Cloud SDKs are optional imports isolated in their provider modules.
14
+
15
+ Workspaces and sandboxes have distinct ownership. A separately acquired workspace can be passed through several agent environments. Promise-based exclusive operations avoid hidden queues or accidental overlap in one sandbox. Git lock files extend that protection across processes; stale process locks can be recovered.
16
+
17
+ Workflows operate on typed task results rather than a global mutable context. Task identity determines value access. Results belong to one execution and cannot leak into a later run. Observer failures are separated from business outcomes.
18
+
19
+ Prompt parsing records command fragments before substituting variables. This preserves command provenance. Native transcripts rewrite only structural `cwd` fields matching the source workspace without performing global string replacement on user messages. Remote transport preserves Git history instead of manufacturing new commits from a patch.
20
+
21
+ Tests combine domain unit tests, deterministic provider contracts, real subprocess/Git functional tests and real container tests in CI. Tests requiring paid remote infrastructure are opt-in. Package smoke tests exercise the actual tarball outside the source checkout.
@@ -0,0 +1,44 @@
1
+ # Migrer vers la version 1.1
2
+
3
+ La version 1.1 complète les campagnes par issue et corrige plusieurs contrats d’exécution. Voici les changements visibles depuis la version 1.0.
4
+
5
+ | Domaine | Comportement en 1.1 |
6
+ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | Boucle ponctuelle | Chaque passe de `dispatch` acquiert une nouvelle sandbox. Utilisez `createSandbox()` pour conserver un environnement entre les passes. |
8
+ | Variables | Seul `.outpost/.env` est lu. Une valeur non vide du fichier est prioritaire ; une déclaration vide reprend la valeur du processus. |
9
+ | Prompts | Les chemins relatifs partent du répertoire de l’appelant, même lorsque `repository` désigne un autre dossier. |
10
+ | Hooks | `workspaceReady` intervient dès l’ouverture du workspace. Les hooks hôte sont séquentiels ; ceux de la sandbox sont concurrents. |
11
+ | Copies | Une entrée absente est ignorée. Les autres erreurs de copie restent bloquantes. |
12
+ | Cloud | Les commits sont envoyés par défaut. `includeUncommitted: true` ajoute les modifications et fichiers non suivis. Les copies demandées explicitement sont transférées. |
13
+ | Branche distante | Sans politique explicite, un workspace d’intégration est créé. |
14
+ | Tokens Claude | `input`, `cached` et `cacheCreated` distinguent entrée non cachée, lecture et création du cache. |
15
+ | Événements | `pass` et `at` identifient chaque observation. Les lignes brutes accompagnent les événements normalisés. |
16
+ | Journaux | Un fichier explicite est complété sans écrasement. `logging: "stdout"` affiche une progression lisible. |
17
+ | Reprise | Un résultat ponctuel accepte une nouvelle branche, un fournisseur et des hooks. Un résultat chaud conserve sa sandbox. |
18
+ | Réponse structurée | La balise et la capacité de réparation sont validées avant allocation. Un bloc Markdown JSON peut apparaître dans la balise. |
19
+
20
+ Le marqueur par défaut reste `<outpost>done</outpost>`. Les adaptateurs natifs restent Codex et Claude Code.
21
+
22
+ ## Campagnes et starters
23
+
24
+ `campaign` utilise `Backlog.list/get/close`, recharge les issues à chaque cycle et ne les ferme qu’après intégration des commits dans la branche hôte. Le connecteur GitHub pagine les résultats et applique son label. Beads doit être installé sur l’hôte ; sa recette de conteneur générée inclut aussi `bd`.
25
+
26
+ Les paramètres du starter sont `cycles`, `concurrency`, `implementationPasses` et `reviewPasses`. `planner: false` traite les issues disponibles dans l’ordre ; `reviewer: false` désactive la revue. Les rôles `planner`, `reviewer` et `merger` acceptent des adaptateurs distincts pour choisir les modèles. Les règles de développement se trouvent dans `.outpost/STANDARDS.md`.
27
+
28
+ Un échec d’implémentation est enregistré sans interrompre les issues indépendantes. Un cycle sans commit s’arrête. Un échec de fusion conserve le travail et ne ferme aucune issue concernée. Si la fermeture chez le tracker échoue après une fusion réussie, vérifiez son état avant de relancer la campagne.
29
+
30
+ `init` ne remplace jamais les fichiers existants : adoptez le nouveau starter manuellement ou générez-le dans un autre dossier.
31
+
32
+ ## Diagnostic et récupération
33
+
34
+ `recoveryDetails(error)` fournit les chemins, commits et journaux disponibles sans remplacer la raison d’annulation. Un échec de préparation nettoie le workspace s’il est propre ; le travail modifié et les artefacts d’une synchronisation échouée sont conservés. Les transferts réussis sont nettoyés à la fermeture.
35
+
36
+ `reporter({ label, verbose, quiet, write })` produit un observateur lisible. `idleWarningMs` règle les avertissements d’inactivité ; `diagnostic` reçoit l’estimation de taille des expansions de prompt. Cette estimation n’est pas un compteur de facturation.
37
+
38
+ ## Extensions
39
+
40
+ Un adaptateur peut fournir `storage: ConversationStore`, `resumable` et `transcriptUsage`. Le service `conversations` expose chemins, recherche, capture, restauration et réécriture sélective. Chaque tour expose son propre chemin `transcript`.
41
+
42
+ Les transferts acceptent `{ signal, deadlineMs }` ; `limits.copyMs` borne leur attente. Un fournisseur personnalisé doit réellement respecter l’annulation pour empêcher des écritures tardives. `Command.terminal` accepte les flux de l’appelant et `attach.ask` complète les variables de prompt manquantes sans terminal interactif.
43
+
44
+ La CI vérifie Linux, Windows, macOS, Docker et Podman. Les contrats Vercel/Daytona sont testés avec des doubles SDK. Une authentification modèle et des comptes cloud distincts restent nécessaires pour les essais réels correspondants.
@@ -0,0 +1,38 @@
1
+ # Migrating to 1.1
2
+
3
+ Version 1.1 completes the issue campaign layer and corrects execution contracts. Review these observable changes when upgrading from 1.0.
4
+
5
+ | Area | 1.1 behavior |
6
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
7
+ | Cold loops | Each pass acquires a fresh sandbox. Use an explicit `createSandbox()` for warm multi-pass execution. |
8
+ | Environment | Only `.outpost/.env` is loaded. Nonempty file values win; empty declarations inherit the process. Declare every key needed inside a sandbox. |
9
+ | Prompt files | Relative paths resolve from the caller's working directory, even if `repository` points elsewhere. |
10
+ | Hooks | `workspaceReady` runs at workspace creation. Host hooks are sequential; sandbox hooks run concurrently. |
11
+ | Optional inputs | Missing `copies` entries are skipped. Other copy errors still fail. |
12
+ | Remote input | Only commits are seeded by default. `includeUncommitted: true` opts into patch/untracked transfer. Selected `copies` are always explicit inputs. |
13
+ | Remote default | An omitted branch policy creates an integration workspace. |
14
+ | Claude usage | `input` contains uncached input; `cached` and `cacheCreated` remain separate counters. |
15
+ | Observations | Every public event has `pass` and `at`. Raw lines accompany normalized events. New phase, summary, result and warning events are available. |
16
+ | Journals | Explicit files append across runs. `logging: "stdout"` renders human-readable progress. |
17
+ | Continuations | Cold result methods accept branch/provider/hook overrides. Warm result methods retain their existing sandbox and reject sandbox reconfiguration. |
18
+ | Structured output | Opening tags and repair capability are checked before provisioning. JSON can use a Markdown code fence inside its tag. |
19
+
20
+ The default completion marker remains `<outpost>done</outpost>`. Native adapters remain Codex and Claude Code.
21
+
22
+ ## Campaigns
23
+
24
+ Regenerate starters in a new directory or manually adopt `campaign`; `init` never overwrites existing files. Campaigns use `Backlog.list/get/close`, refresh the backlog every cycle and close issues only after their commits reach the host branch. The GitHub connector paginates and applies its configured label. Beads runs on the host and must be installed there; its generated container recipe also includes `bd`.
25
+
26
+ Set `cycles`, `concurrency`, `implementationPasses` and `reviewPasses` in the starter. `planner: false` takes ready issues in order. `reviewer: false` skips review. Set separate adapters for `planner`, `reviewer` and `merger` to choose different models.
27
+
28
+ ## Recovery and observability
29
+
30
+ `recoveryDetails(error)` returns available branch, directory, commits, transcript and log information. It preserves cancellation reason identity. A preparation failure removes a clean owned workspace; changed workspaces and failed remote recovery files survive. Successful remote transfer staging is cleaned at close.
31
+
32
+ `reporter({ label, verbose, quiet, write })` creates a public observation callback. `idleWarningMs` controls periodic inactivity messages; `diagnostic` receives estimated token sizes of prompt command expansions. Estimates are not billing counts.
33
+
34
+ ## Extension ports
35
+
36
+ Custom agents can provide `storage: ConversationStore`, `resumable`, and `transcriptUsage`. `conversations` exposes native path, search, capture, restore and selective rewrite helpers. Per-turn results expose their own `transcript` path.
37
+
38
+ Transfers accept optional `{ signal, deadlineMs }`. `limits.copyMs` bounds orchestration transfers. A custom provider must honor cancellation itself to prevent background side effects. `Command.terminal` accepts caller-owned input/output/error streams; `attach.ask` supplies missing file-prompt variables without a TTY.
@@ -0,0 +1,101 @@
1
+ # Operations and releases
2
+
3
+ ## Repository topology
4
+
5
+ GitLab is the writable source of truth:
6
+
7
+ ```text
8
+ https://gitlab.elielaloum.com/elielaloum/outpost.git
9
+ push mirror → https://github.com/elie-laloum/outpost.git
10
+ GitHub Actions → packages and releases
11
+ ```
12
+
13
+ Create commits and tags on GitLab. The push mirror includes branches and tags and preserves divergent refs rather than silently overwriting them. Do not merge changes directly on GitHub; bring contributions back to GitLab. Mirror credentials belong in GitLab's protected mirror configuration, never in tracked files or local remote URLs.
14
+
15
+ ## Install a released archive
16
+
17
+ Download the `.tgz` from [GitHub Releases](https://github.com/elie-laloum/outpost/releases), then:
18
+
19
+ ```sh
20
+ npm install --save-dev ./elie-laloum-outpost-1.1.0.tgz
21
+ npx outpost --help
22
+ ```
23
+
24
+ The archive includes JavaScript, declarations, source maps, EN/FR READMEs and the license. Main imports and local/container providers have no mandatory runtime npm dependencies. Cloud SDK peers are optional and installed only when needed.
25
+
26
+ For GitHub Packages, configure the scope in your project's `.npmrc`:
27
+
28
+ ```ini
29
+ @elie-laloum:registry=https://npm.pkg.github.com
30
+ ```
31
+
32
+ Authenticate to that registry using an appropriate package-read token or your organization's supported authentication flow, then install `@elie-laloum/outpost`. GitHub Packages can require authentication even for public packages. Do not commit authentication tokens. The public release archive avoids registry authentication.
33
+
34
+ ## Local development
35
+
36
+ ```sh
37
+ npm ci
38
+ npm run typecheck
39
+ npm test
40
+ npm run coverage
41
+ npm run build
42
+ npm run test:package
43
+ npm run format:check
44
+ ```
45
+
46
+ Tests use temporary Git repositories and fixture agents, so unit/functional tests require no paid model access. `test:package` installs the actual archive in a separate temporary project and checks imports and initialization. `test:container` requires `OUTPOST_CONTAINER_ENGINE=docker` or `podman` and an `outpost-ci:latest` image generated by the CLI.
47
+
48
+ The CI matrix covers Linux, Windows and macOS. Docker and Podman jobs build the generated image, verify both native CLIs, edit/commit through mounted Git, round-trip binary files and nested directories, verify file-mount parent permissions, test output/environment handling and verify cancellation does not destroy the warm sandbox or leave a delayed writer alive. SDK contract tests cover Vercel and Daytona without provisioning cloud resources.
49
+
50
+ Live model/cloud tests require separate provider credentials. Contract tests and CLI compatibility checks do not prove availability or model behavior in a specific paid account. Pin custom images and use account-specific smoke tests before broad deployment.
51
+
52
+ ## Release procedure
53
+
54
+ 1. Change `package.json` and `package-lock.json` to the intended stable version and update `CHANGELOG.md` on GitLab.
55
+ 2. Wait for GitHub CI on that mirrored commit to pass.
56
+ 3. Create `v<version>` on GitLab at that exact commit.
57
+ 4. The mirrored tag starts `Release`. It verifies the version, runs the complete reusable CI workflow, builds the package and publishes to GitHub Packages using its short-lived `GITHUB_TOKEN`.
58
+ 5. The workflow creates a GitHub release with the installable tarball. Confirm the package and release point to the expected version.
59
+
60
+ The publishing job has `packages: write` and `contents: write`; ordinary CI only has `contents: read`. Checkout does not persist credentials. No long-lived package-publishing token is needed for GitHub Packages.
61
+
62
+ For GitHub Packages, the release job sets the archive's repository metadata to the GitHub mirror so package permissions attach to that workflow repository. The tracked source manifest, writable repository and tag creation remain on GitLab.
63
+
64
+ Optional npm publication is disabled until repository variable `NPM_PUBLISH` is `true`. Before enabling it, configure the npm package's trusted publisher for GitHub owner `elie-laloum`, repository `outpost`, workflow `release.yml`. The job uses OIDC and provenance. Account/namespace ownership and the registry's initial package setup must be completed by an authorized npm account. Enabling a variable alone does not establish that trust.
65
+
66
+ Published package versions are immutable. If a release fails after publication, inspect the registry before rerunning to avoid duplicate publication errors; use a new patch version for changed content. Never move a released tag.
67
+
68
+ ## Recovery
69
+
70
+ Every failure should retain enough context to inspect the workspace. `OutpostError.details` includes available paths; `recoveryDetails(error)` includes collected conversation, commit, log and workspace information without changing the error identity. Clean workspaces are removed after startup failure; dirty ones are retained. Dirty managed worktrees remain under `.outpost/workspaces`, and named branches remain after clean worktree removal.
71
+
72
+ Remote transfer folders under `.outpost/recovery` may contain:
73
+
74
+ - `initial.bundle` / `commits.bundle`: Git objects with original history.
75
+ - `initial.patch` / `remote.patch` / `previous.patch`: binary-capable worktree changes.
76
+ - `previous-index.patch`: the prior staged changes.
77
+ - `incoming/` / `previous-files/`: untracked file copies.
78
+ - `state.json`: synchronization endpoints and file lists.
79
+
80
+ Inspect these before attempting recovery. Fetch a bundle into a separate recovery clone and review the patch there before applying it to valuable work. An incomplete network transfer is not guaranteed to contain every remote file; the original error identifies the failed stage. Keep the cloud sandbox accessible when performing custom manual recovery.
81
+
82
+ Lock files record their owner PID. Active locks reject conflicting workspace use; dead owners can be recovered automatically. If a PID has been reused, verify ownership before manually deleting a lock. Avoid editing a local workspace while its remote sandbox is active.
83
+
84
+ ## Logs and credentials
85
+
86
+ Logs are JSONL under `.outpost/logs`; prompts, text, tool events, conversation IDs and usage are recorded. Verbose mode additionally retains raw protocol events. Native transcripts use the agent's own host storage. Files can contain private data; choose retention and backup policies accordingly. Runtime folders are ignored by Git. Successful remote transfer staging is removed at closure; failed recovery artifacts and native transcripts are retained. Explicit `await sandbox.close()` is the reliable shutdown path. SIGINT/SIGTERM await registered cleanup; `process.exit()` can only perform synchronous cleanup, including container removal. Asynchronous cloud cleanup cannot be guaranteed during a forced process exit.
87
+
88
+ Use `.outpost/.env` for task-specific secrets and `.env.example` for names only. Explicitly mount authentication files only when needed, preferably read-only. Container environment values are transported through per-command aliases, preserving the engine's own host environment and keeping values out of command arguments.
89
+
90
+ ## Troubleshooting
91
+
92
+ | Symptom | Check |
93
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------ |
94
+ | Image not found or UID mismatch | Build with the same engine, image name and UID/GID used by the provider. |
95
+ | Podman cannot write the worktree | Verify rootless user namespaces, host permissions and SELinux labels. |
96
+ | Agent not authenticated | Check the agent API key/OAuth variable or its explicitly mounted native auth file. |
97
+ | Cold resume cannot find a transcript | Confirm agent, conversation ID and `conversationHome`; capture must have succeeded. |
98
+ | Host changed during remote execution | Preserve the recovery folder, resolve local edits and inspect incoming history separately. |
99
+ | Workflow never finishes after cancellation | Ensure custom task callbacks and custom providers honor their `AbortSignal`. |
100
+ | Native CLI rejects a flag | Compare installed versions with exported `agentVersions`; rebuild a stale image. |
101
+ | No GitHub CI after a GitLab push | Inspect the GitLab push mirror status and its token permissions. |
@@ -0,0 +1,119 @@
1
+ # Sandbox providers
2
+
3
+ | Provider | Placement | Terminal | File transfer | Requirement |
4
+ | -------------- | ------------- | -------- | ----------------- | ------------------------------------- |
5
+ | Docker | Mounted | Yes | Files/directories | Running Docker daemon |
6
+ | Podman | Mounted | Yes | Files/directories | Running Podman engine |
7
+ | Host `local()` | Host checkout | Yes | Files/directories | Native agent installed |
8
+ | Vercel | Remote | No | Files/directories | `@vercel/sandbox`, Vercel credentials |
9
+ | Daytona | Remote | No | Files/directories | `@daytona/sdk`, Daytona credentials |
10
+
11
+ ## Docker and Podman
12
+
13
+ ```ts
14
+ import { docker } from "@elie-laloum/outpost/providers/docker";
15
+
16
+ const provider = docker({
17
+ image: "outpost:my-repository",
18
+ user: { uid: 1000, gid: 1000 },
19
+ volumes: [
20
+ {
21
+ source: "~/.config/example",
22
+ target: "/home/agent/.config/example",
23
+ readOnly: true,
24
+ },
25
+ ],
26
+ networks: ["development"],
27
+ groups: [1000],
28
+ devices: [],
29
+ cpus: 2,
30
+ memoryMb: 4096,
31
+ label: "z",
32
+ retain: 65_536,
33
+ variables: {},
34
+ });
35
+ ```
36
+
37
+ `podman()` accepts the same options. Default image is `outpost:<normalized-repository-directory>`. Build it with the CLI. Default UID/GID match the host on POSIX and use 1000 on Windows. The image preflight reports numeric UID mismatches when the user was not explicitly overridden. Podman supports `userns: "keep-id" | false` and maps explicit UIDs/GIDs. macOS checks that Podman Machine is running. Mount sources support `~`, relative and absolute paths, and files. Relative targets resolve under `/workspace`; `~` and `~/...` resolve under the agent home. Individual file mounts must target the home: mount a directory for other destinations. Their parents are prepared for the agent UID/GID. Linux mounts use SELinux `z` by default; choose `Z` for a private label or `false` to omit labels. Windows/macOS use bind-mount syntax.
38
+
39
+ Containers have a private ephemeral home, dropped capabilities, no-new-privileges and an init process. Only selected mounts and Git metadata are exposed. Git paths are remapped so Windows worktree pointers remain usable inside Linux. Credentials are passed through environment names rather than command-line values. A command-specific process group allows cancellation without destroying the warm environment.
40
+
41
+ The generated image contains Git, Node.js, Python, shell utilities and both agent CLIs. Modify the Dockerfile/Containerfile to install project tools. `outpost image build --file custom.Dockerfile --image custom:tag --uid 1000 --gid 1000` selects an alternative recipe. `outpost image remove` removes only the selected image.
42
+
43
+ ## Explicit host execution
44
+
45
+ ```ts
46
+ import { local } from "@elie-laloum/outpost/providers/local";
47
+ const result = await dispatch({
48
+ agent: codex(),
49
+ provider: local(),
50
+ brief: { text: "Inspect the project." },
51
+ });
52
+ ```
53
+
54
+ This is unisolated execution using your OS account and environment. Install/authenticate the CLI on the host. Outpost never silently falls back to host execution if a container fails. The host adapter does not elevate privileges.
55
+
56
+ ## Vercel
57
+
58
+ ```sh
59
+ npm install @vercel/sandbox
60
+ ```
61
+
62
+ ```ts
63
+ import { vercel } from "@elie-laloum/outpost/providers/vercel";
64
+
65
+ const provider = vercel({
66
+ create: { timeout: 30 * 60 * 1000 },
67
+ variables: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
68
+ });
69
+ ```
70
+
71
+ `create` accepts the installed SDK's sandbox-creation options, including credentials, runtime/image, networking and resources. Credential discovery follows the SDK. `root` can override the remote workspace directory; `retain` controls captured output tails. The provider discovers the actual remote home. Outpost installs the chosen agent in a user-writable prefix if it is missing; set `bootstrap: false` on `createSandbox`/`dispatch` when managing tools yourself.
72
+
73
+ ## Daytona
74
+
75
+ ```sh
76
+ npm install @daytona/sdk
77
+ ```
78
+
79
+ ```ts
80
+ import { daytona } from "@elie-laloum/outpost/providers/daytona";
81
+
82
+ const provider = daytona({
83
+ connection: { apiKey: process.env.DAYTONA_API_KEY },
84
+ create: { language: "typescript" },
85
+ });
86
+ ```
87
+
88
+ `connection` is the SDK client configuration. `create` accepts an image/snapshot sandbox configuration. The remote environment must provide Node.js, npm, Git, `sh` and `setsid`. `root`, `variables`, and `retain` are available. Native process sessions stream both output channels and are cleaned up after each invocation. Cancellation terminates only the command process group.
89
+
90
+ ## Remote Git transport
91
+
92
+ Remote workspaces default to temporary integration branches and start from committed Git history. `includeUncommitted: true` explicitly sends the host patch and untracked files. Otherwise pre-existing host edits are preserved and overlapping remote edits are rejected before local mutation. Selected copied inputs are uploaded too. Each dispatch, command or terminal completion synchronizes back. New commits retain their object IDs, authors, timestamps and parent relationships. Repeated synchronization handles uncommitted work later becoming committed without duplicating commits.
93
+
94
+ Outpost compares host state with its last synchronized state. Concurrent local edits cause a recovery error. Before applying remote changes it saves patches, untracked files and incoming commits in `.outpost/recovery`. Rewritten/non-fast-forward remote history is rejected. Relative transfer paths are validated, and local symlink-parent traversal is rejected.
95
+
96
+ ## Custom providers
97
+
98
+ `mountedProvider({ name, variables?, acquire })` and `remoteProvider({ name, variables?, acquire })` wrap your own backend. `acquire(context)` receives repository/workspace paths, Git metadata directories, resolved variables and a signal. Return a `SandboxLease`:
99
+
100
+ ```ts
101
+ interface SandboxLease {
102
+ root: string;
103
+ home: string;
104
+ invoke(command: Command): Promise<CommandResult>;
105
+ upload(
106
+ source: string,
107
+ destination: string,
108
+ options?: TransferOptions,
109
+ ): Promise<void>;
110
+ download(
111
+ source: string,
112
+ destination: string,
113
+ options?: TransferOptions,
114
+ ): Promise<void>;
115
+ release(): Promise<void>;
116
+ }
117
+ ```
118
+
119
+ `invoke` must stream output to `observe`, respect abort/deadline, and leave the lease reusable after cancellation. `release` must be idempotent. Transfer options contain `signal` and `deadlineMs`; custom transfers must stop writing after cancellation. Outpost bounds the wait, but cannot forcibly cancel arbitrary user callbacks. Cloud SDK requests already sent may finish remotely; built-in downloads check cancellation before writing returned bytes. Remote providers must support Git and file transfer; Outpost performs synchronization through those capabilities. Interactive support is optional but unsupported calls must fail clearly. Cloud factories also accept an optional connection factory for contract tests or custom SDK wiring.
@@ -0,0 +1,101 @@
1
+ # Workflows
2
+
3
+ Tasks form a directed acyclic graph. Each task declares its dependencies with `after`, and can only read successful values from those dependencies. Declaration order does not affect scheduling. A workflow validates duplicate keys, missing dependencies and cycles before any task runs.
4
+
5
+ ```ts
6
+ import { task, workflow } from "@elie-laloum/outpost";
7
+
8
+ const analyze = task({
9
+ key: "analyze",
10
+ perform: async () => ({ needed: true }),
11
+ });
12
+ const change = task({
13
+ key: "change",
14
+ after: [analyze],
15
+ condition: (context) => context.value(analyze).needed,
16
+ timeoutMs: 300_000,
17
+ retry: {
18
+ attempts: 3,
19
+ delayMs: 500,
20
+ accepts: (error) => error instanceof Error,
21
+ },
22
+ perform: async (context) => {
23
+ context.signal.throwIfAborted();
24
+ return "changed";
25
+ },
26
+ });
27
+
28
+ const delivery = workflow("delivery", [analyze, change]);
29
+ console.log(delivery.diagram());
30
+ const result = await delivery.start({ concurrency: 2, stopOnError: true });
31
+ result.unwrap();
32
+ console.log(result.value(change));
33
+ ```
34
+
35
+ `TaskContext` includes `signal`, one-based `attempt`, `executionId`, and typed `value(task)`. A condition runs before the first attempt. False conditions skip the task and its descendants. Retries occur only on failure, within the declared attempt budget, and can be filtered with `accepts`. Delays respect cancellation.
36
+
37
+ Task deadlines are **cooperative**: task code must honor `context.signal`. The scheduler waits for active work to clean up, preventing abandoned work from mutating resources after a workflow returns. Sandbox operations honor the signal. Arbitrary callbacks that ignore it cannot be forcibly stopped by JavaScript.
38
+
39
+ `stopOnError` defaults to true and aborts active siblings after the first failure. Set false to let independent branches finish. Failed/skipped dependencies skip descendants. External cancellation returns a cancelled workflow. Observers receive start/task/retry/finish events and cannot change outcomes; their exceptions are collected in `observerErrors`.
40
+
41
+ `WorkflowResult` contains immutable task records, errors, observer errors, status, execution ID, typed value access and `unwrap()`. `unwrap` throws `WorkflowFailure` for failed/cancelled results. The error retains the full result.
42
+
43
+ ## Sandbox tasks
44
+
45
+ - `agentTask({ key, sandbox, request })` dispatches through an existing sandbox. `request(context)` produces the prompt/options.
46
+ - `commandTask({ key, sandbox, command })` executes a command and fails the task on nonzero status. The command may be a function of context.
47
+ - `isolatedTask({ key, request })` owns a one-shot sandbox/workspace for that task. `request(context)` supplies the complete dispatch configuration.
48
+
49
+ Use dependencies to serialize tasks sharing a sandbox. Use `isolatedTask` with distinct named workspaces for fanout. Parallel temporary integration branches serialize their host merge stage, but semantically conflicting changes still require human resolution.
50
+
51
+ ## Issue campaigns
52
+
53
+ `campaign` is the application-level orchestration service for issue delivery. A `Backlog` supplies `list(signal)`, `get(id, signal)` and `close(id, signal)`. Each issue has an id, title, optional body and optional `blockedBy` ids. Blocked issues are excluded while their dependencies remain open.
54
+
55
+ ```ts
56
+ import { campaign, codex, claude, githubBacklog } from "@elie-laloum/outpost";
57
+ import { docker } from "@elie-laloum/outpost/providers/docker";
58
+
59
+ const result = await campaign({
60
+ agent: codex(),
61
+ planner: claude(),
62
+ reviewer: codex(),
63
+ merger: codex(),
64
+ provider: docker(),
65
+ backlog: githubBacklog({ label: "outpost-ready" }),
66
+ cycles: 10,
67
+ concurrency: 3,
68
+ implementationPasses: 100,
69
+ reviewPasses: 1,
70
+ standards:
71
+ "Follow repository conventions, test behavior and keep comments brief.",
72
+ observe: (event) => console.log(event.phase, event.cycle, event.issue),
73
+ });
74
+ console.log(result.reason, result.issues);
75
+ ```
76
+
77
+ Each cycle refreshes the backlog. The planner returns validated assignments with unique issue ids and new branch names. Unknown issues, invalid branches and duplicate assignments fail before implementation. `planner: false` takes ready issues in order, up to the concurrency limit.
78
+
79
+ Each issue owns a named workspace and one sandbox. Implementation and optional review share that sandbox; review inspects the full diff against the cycle's base commit. An implementation without commits skips review and closure. A failed issue is recorded while independent issues finish. `reviewer: false` disables review.
80
+
81
+ Completed branches enter a separate integration sandbox, even when only one branch completed. The merger resolves conflicts, validates the result and commits corrections. Outpost verifies that the original issue commits are ancestors of the integrated result and rejects uncommitted leftovers. Only after integration into the host branch does it close the issues. Merge failure retains the workspace; tracker closure failure is reported after integration and requires reconciliation before rerunning.
82
+
83
+ The campaign stops on an empty or blocked backlog, no progress, cancellation, or the cycle limit. The result distinguishes `empty`, `blocked`, `no-progress` and `limit`, and records each issue as `empty`, `failed` or `merged`. Positive integer limits are validated before work starts.
84
+
85
+ ## Starter templates
86
+
87
+ | Template | Structure |
88
+ | ------------- | ---------------------------------------------------------------------------------- |
89
+ | `blank` | One dispatch with a configurable objective. |
90
+ | `iterate` | Sequential campaign; reloads the tracker each cycle. |
91
+ | `review` | Sequential implementation and conditional warm review per issue. |
92
+ | `plan` | Typed plan, separate issue branches, bounded concurrent execution and integration. |
93
+ | `plan-review` | Planned parallel execution with a warm review for each changed issue. |
94
+
95
+ Without a tracker, a campaign starter wraps the command-line objective as one in-memory issue. With `--tracker github`, `beads` or `custom`, the tracker supplies the workload. Limits and role adapters are editable at the top level of the generated call; standards live in `.outpost/STANDARDS.md`.
96
+
97
+ GitHub uses authenticated host `gh`, paginates all issue pages, excludes pull requests and applies the configured label. `--label NAME` creates/updates the label and configures filtering. Declare `GH_TOKEN` in `.outpost/.env` or authenticate `gh` directly. The connector follows the official [GitHub CLI pagination contract](https://cli.github.com/manual/gh_api).
98
+
99
+ Beads uses host `bd ready --json --limit 0`, `bd show` and `bd close`. Install and initialize [Beads](https://github.com/gastownhall/beads) on the host before running. Selecting Beads also installs its pinned CLI in the generated container image.
100
+
101
+ The custom starter implements three HTTP operations: GET `issues?state=open`, GET `issues/:id`, POST `issues/:id/close`. Set `OUTPOST_TRACKER_URL` on the host and adapt authentication/pagination in `tickets.ts` or `tickets.mts`. `.outpost/TRACKER.md` documents the contract. Keep tracker mutation in the host connector so agents do not close issues before integration.