@ashulab/carrier 0.0.0-stage → 0.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.
- package/LICENSE +21 -0
- package/README.md +264 -2
- package/dist/chat/claude.js +367 -0
- package/dist/chat/codex.js +375 -0
- package/dist/chat/config.js +61 -0
- package/dist/chat/db.js +9 -0
- package/dist/chat/fake.js +203 -0
- package/dist/chat/folder.js +56 -0
- package/dist/chat/git.js +27 -0
- package/dist/chat/group.js +28 -0
- package/dist/chat/health.js +49 -0
- package/dist/chat/host.js +123 -0
- package/dist/chat/idle.js +20 -0
- package/dist/chat/migrations/0000_lame_quasar.sql +15 -0
- package/dist/chat/migrations/0001_quiet_penance.sql +23 -0
- package/dist/chat/migrations/0002_slim_ezekiel_stane.sql +1 -0
- package/dist/chat/migrations/0003_naive_iron_lad.sql +5 -0
- package/dist/chat/migrations/0004_skinny_dakota_north.sql +1 -0
- package/dist/chat/migrations/0005_tidy_strong_guy.sql +1 -0
- package/dist/chat/migrations/meta/0000_snapshot.json +121 -0
- package/dist/chat/migrations/meta/0001_snapshot.json +277 -0
- package/dist/chat/migrations/meta/0002_snapshot.json +284 -0
- package/dist/chat/migrations/meta/0003_snapshot.json +320 -0
- package/dist/chat/migrations/meta/0004_snapshot.json +327 -0
- package/dist/chat/migrations/meta/0005_snapshot.json +334 -0
- package/dist/chat/migrations/meta/_journal.json +48 -0
- package/dist/chat/names.js +97 -0
- package/dist/chat/plan.js +21 -0
- package/dist/chat/projects.js +242 -0
- package/dist/chat/runner.js +6 -0
- package/dist/chat/schema.js +48 -0
- package/dist/chat/server.js +202 -0
- package/dist/chat/sessions.js +766 -0
- package/dist/chat/sweep.js +64 -0
- package/dist/chat/transcript.js +31 -0
- package/dist/chat/words.js +20 -0
- package/dist/chat/workspace.js +134 -0
- package/dist/chat/worktree.js +142 -0
- package/dist/config/default-path.js +7 -0
- package/dist/config/engine-places.js +9 -0
- package/dist/config/json-schema.js +15 -0
- package/dist/config/load.js +48 -0
- package/dist/config/resolve.js +41 -0
- package/dist/config/schema.js +225 -0
- package/dist/control/epic.js +219 -0
- package/dist/control/events.js +48 -0
- package/dist/control/files.js +262 -0
- package/dist/control/replies.js +1 -0
- package/dist/control/server.js +237 -0
- package/dist/control/sessions.js +68 -0
- package/dist/doctor/doctor.js +255 -0
- package/dist/doctor/fixes.js +35 -0
- package/dist/execution/agent-runner.js +1 -0
- package/dist/execution/caller-result.js +61 -0
- package/dist/execution/events.js +19 -0
- package/dist/execution/failure.js +3 -0
- package/dist/execution/fake.js +82 -0
- package/dist/execution/git-workspaces.js +136 -0
- package/dist/execution/import-result.js +23 -0
- package/dist/execution/workspaces.js +1 -0
- package/dist/format/duration.js +12 -0
- package/dist/harnesses/agy.js +131 -0
- package/dist/harnesses/claude.js +246 -0
- package/dist/harnesses/codex.js +118 -0
- package/dist/harnesses/common.js +59 -0
- package/dist/harnesses/credentials.js +38 -0
- package/dist/harnesses/host-runner.js +225 -0
- package/dist/harnesses/local.js +25 -0
- package/dist/harnesses/names.js +9 -0
- package/dist/harnesses/replay-events.js +57 -0
- package/dist/integrations/available-repos.js +29 -0
- package/dist/integrations/forge-cwd.js +27 -0
- package/dist/integrations/github-graphql.js +216 -0
- package/dist/integrations/github.js +231 -0
- package/dist/logging/logger.js +53 -0
- package/dist/logging/redact.js +21 -0
- package/dist/orchestration/blockers.js +25 -0
- package/dist/orchestration/changes-slices.js +5 -0
- package/dist/orchestration/clock.js +7 -0
- package/dist/orchestration/engine.js +1068 -0
- package/dist/orchestration/fake.js +152 -0
- package/dist/orchestration/final-pr.js +15 -0
- package/dist/orchestration/github.js +31 -0
- package/dist/orchestration/reserve.js +32 -0
- package/dist/orchestration/resume-refused.js +7 -0
- package/dist/orchestration/retention.js +145 -0
- package/dist/orchestration/retry.js +16 -0
- package/dist/prompts/build-prompt.js +13 -0
- package/dist/prompts/fix.js +21 -0
- package/dist/prompts/intro.js +21 -0
- package/dist/prompts/invoke.js +2 -0
- package/dist/prompts/persona.js +12 -0
- package/dist/prompts/result.js +32 -0
- package/dist/prompts/review.js +15 -0
- package/dist/prompts/ship.js +8 -0
- package/dist/status/snapshot.js +92 -0
- package/dist/status/types.js +1 -0
- package/dist/storage/caller-results.js +7 -0
- package/dist/storage/db.js +8 -0
- package/dist/storage/epic-review-threads.js +19 -0
- package/dist/storage/epic-slices.js +17 -0
- package/dist/storage/events.js +52 -0
- package/dist/storage/executions.js +31 -0
- package/dist/storage/lock.js +21 -0
- package/dist/storage/migrations/0000_nosy_demogoblin.sql +5 -0
- package/dist/storage/migrations/0001_worried_grey_gargoyle.sql +3 -0
- package/dist/storage/migrations/0002_tough_squirrel_girl.sql +5 -0
- package/dist/storage/migrations/0003_loose_maria_hill.sql +4 -0
- package/dist/storage/migrations/0004_long_cerise.sql +18 -0
- package/dist/storage/migrations/0005_review_result.sql +2 -0
- package/dist/storage/migrations/0006_melted_menace.sql +10 -0
- package/dist/storage/migrations/0007_powerful_bloodscream.sql +2 -0
- package/dist/storage/migrations/0008_panel_state.sql +4 -0
- package/dist/storage/migrations/0009_workable_tiger_shark.sql +5 -0
- package/dist/storage/migrations/0010_nasty_blue_marvel.sql +4 -0
- package/dist/storage/migrations/0011_steep_lila_cheney.sql +13 -0
- package/dist/storage/migrations/0012_talented_big_bertha.sql +1 -0
- package/dist/storage/migrations/0013_third_mulholland_black.sql +17 -0
- package/dist/storage/migrations/0014_perfect_marten_broadcloak.sql +20 -0
- package/dist/storage/migrations/0015_long_bloodaxe.sql +1 -0
- package/dist/storage/migrations/0016_neat_true_believers.sql +5 -0
- package/dist/storage/migrations/0017_previous_swarm.sql +6 -0
- package/dist/storage/migrations/0018_opposite_plazm.sql +2 -0
- package/dist/storage/migrations/0019_hesitant_triton.sql +3 -0
- package/dist/storage/migrations/meta/0000_snapshot.json +49 -0
- package/dist/storage/migrations/meta/0001_snapshot.json +74 -0
- package/dist/storage/migrations/meta/0002_snapshot.json +105 -0
- package/dist/storage/migrations/meta/0003_snapshot.json +134 -0
- package/dist/storage/migrations/meta/0004_snapshot.json +257 -0
- package/dist/storage/migrations/meta/0005_snapshot.json +271 -0
- package/dist/storage/migrations/meta/0006_snapshot.json +337 -0
- package/dist/storage/migrations/meta/0007_snapshot.json +351 -0
- package/dist/storage/migrations/meta/0008_snapshot.json +379 -0
- package/dist/storage/migrations/meta/0009_snapshot.json +414 -0
- package/dist/storage/migrations/meta/0010_snapshot.json +442 -0
- package/dist/storage/migrations/meta/0011_snapshot.json +531 -0
- package/dist/storage/migrations/meta/0012_snapshot.json +538 -0
- package/dist/storage/migrations/meta/0013_snapshot.json +655 -0
- package/dist/storage/migrations/meta/0014_snapshot.json +772 -0
- package/dist/storage/migrations/meta/0015_snapshot.json +779 -0
- package/dist/storage/migrations/meta/0016_snapshot.json +793 -0
- package/dist/storage/migrations/meta/0017_snapshot.json +835 -0
- package/dist/storage/migrations/meta/0018_snapshot.json +849 -0
- package/dist/storage/migrations/meta/0019_snapshot.json +870 -0
- package/dist/storage/migrations/meta/_journal.json +146 -0
- package/dist/storage/schema.js +43 -0
- package/dist/terminal/cli/args.js +91 -0
- package/dist/terminal/cli/cli-error.js +11 -0
- package/dist/terminal/cli/commands/chat-host.js +18 -0
- package/dist/terminal/cli/commands/chat.js +321 -0
- package/dist/terminal/cli/commands/control.js +46 -0
- package/dist/terminal/cli/commands/doctor.js +15 -0
- package/dist/terminal/cli/commands/init.js +43 -0
- package/dist/terminal/cli/commands/restart.js +92 -0
- package/dist/terminal/cli/commands/run.js +20 -0
- package/dist/terminal/cli/commands/schema.js +4 -0
- package/dist/terminal/cli/commands/start.js +21 -0
- package/dist/terminal/cli/commands/status.js +61 -0
- package/dist/terminal/cli/commands/watch.js +112 -0
- package/dist/terminal/cli/compose.js +163 -0
- package/dist/terminal/cli/config-file.js +24 -0
- package/dist/terminal/cli/control-client.js +94 -0
- package/dist/terminal/cli/detached.js +41 -0
- package/dist/terminal/cli/interrupt.js +39 -0
- package/dist/terminal/cli/main.js +85 -0
- package/dist/terminal/cli/open-url.js +9 -0
- package/dist/terminal/dashboard/dashboard.js +204 -0
- package/dist/terminal/dashboard/history.js +38 -0
- package/dist/terminal/dashboard/interactive.js +60 -0
- package/dist/terminal/dashboard/keys.js +22 -0
- package/dist/terminal/dashboard/logs.js +85 -0
- package/dist/terminal/dashboard/panel.js +62 -0
- package/dist/terminal/dashboard/plain.js +32 -0
- package/dist/terminal/dashboard/printable.js +2 -0
- package/dist/terminal/dashboard/ui-state.js +110 -0
- package/dist/terminal/prompts/clack.js +121 -0
- package/dist/terminal/prompts/types.js +3 -0
- package/dist/terminal/wizard/build-config.js +66 -0
- package/dist/terminal/wizard/deps.js +94 -0
- package/dist/terminal/wizard/flags.js +1 -0
- package/dist/terminal/wizard/forge-registry.js +23 -0
- package/dist/terminal/wizard/prior.js +20 -0
- package/dist/terminal/wizard/questions/agent.js +61 -0
- package/dist/terminal/wizard/questions/identities.js +28 -0
- package/dist/terminal/wizard/questions/identity.js +30 -0
- package/dist/terminal/wizard/questions/options.js +93 -0
- package/dist/terminal/wizard/questions/out.js +16 -0
- package/dist/terminal/wizard/questions/persona.js +47 -0
- package/dist/terminal/wizard/questions/repos.js +61 -0
- package/dist/terminal/wizard/summary.js +31 -0
- package/dist/terminal/wizard/testing.js +106 -0
- package/dist/terminal/wizard/wizard.js +172 -0
- package/package.json +46 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ashu Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,265 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">Carrier</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
An autonomous epic orchestrator that runs on your machine.<br />
|
|
5
|
+
It watches your GitHub repos for epics marked ready, has one AI agent implement them with <code>ship</code>, has a second agent with a different identity review the final PR, and hands you an epic that is ready to test.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<img alt="Node.js 22+" src="https://img.shields.io/badge/Node.js-22%2B-339933?logo=nodedotjs&logoColor=white" />
|
|
10
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-ESM-3178c6?logo=typescript&logoColor=white" />
|
|
11
|
+
<img alt="SQLite" src="https://img.shields.io/badge/SQLite-better--sqlite3-003b57?logo=sqlite&logoColor=white" />
|
|
12
|
+
<img alt="Drizzle ORM" src="https://img.shields.io/badge/Drizzle-ORM-c5f74f?logo=drizzle&logoColor=black" />
|
|
13
|
+
<img alt="Zod" src="https://img.shields.io/badge/Zod-validation-3e67b1?logo=zod&logoColor=white" />
|
|
14
|
+
<img alt="Vitest" src="https://img.shields.io/badge/Vitest-tests-6e9f18?logo=vitest&logoColor=white" />
|
|
15
|
+
<img alt="pnpm" src="https://img.shields.io/badge/pnpm-12-f69220?logo=pnpm&logoColor=white" />
|
|
16
|
+
<img alt="Claude" src="https://img.shields.io/badge/Claude-CLI-d97757?logo=anthropic&logoColor=white" />
|
|
17
|
+
<img alt="Codex" src="https://img.shields.io/badge/Codex-CLI-000000?logo=openai&logoColor=white" />
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img alt="The Carrier macOS app showing a finished epic: its stages, the agent output of each review round and the merged slices" src="docs/assets/app-overview.png" width="900" />
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
> **Status:** first version, macOS only, built for personal use. Agents run as the regular `claude`, `codex` and `agy` CLIs with **your permissions** and no isolation. Read [Security](#security) before pointing it at anything you care about.
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
Carrier is a long-running process: a terminal app, or in the background with `carrier start`. You start it from any folder with a JSON config; it polls the repos you list and, for every epic labelled `solve:epic:ready`, drives the whole loop without supervision:
|
|
29
|
+
|
|
30
|
+
1. **Pick up.** Each interval it registers new ready epics in arrival order, and when a slot is free it takes the oldest one and relabels it `solve:epic:running`.
|
|
31
|
+
2. **Ship.** An _author_ agent (Claude Code, Codex or Antigravity) runs [Solve](https://github.com/AshuLab/skills)'s `ship` on the epic inside a managed clone. Carrier then checks the final integration PR against GitHub: it exists, is open, references the epic, and its head is the expected commit.
|
|
32
|
+
3. **Review.** A _reviewer_ agent with a **different GitHub identity** reviews that PR in a separate checkout. If it asks for changes, the author gets a fix turn and the reviewer goes again, up to `maxReviews` rounds.
|
|
33
|
+
4. **Hand-off.** The epic ends as `solve:epic:ready-for-test` (a conclusive review on the current head) or `solve:epic:needs-human` (stopped, with the reason shown in the terminal). Carrier never merges the final PR — you test it and decide.
|
|
34
|
+
|
|
35
|
+
It is built to survive interruptions: SQLite is the source of truth and labels are its mirror, technical failures are retried by a new execution that first verifies the real state (no duplicate PRs, comments or commits), `Ctrl+C` stops the agents but keeps sessions and work, and a restart reconciles against GitHub before continuing.
|
|
36
|
+
|
|
37
|
+
## What's included
|
|
38
|
+
|
|
39
|
+
- **Setup** — `carrier init` (guided, or all flags) writes the config; `carrier doctor` checks it and the machine; `carrier schema` prints the config's JSON Schema.
|
|
40
|
+
- **Run without a terminal** — `carrier start --config <path>` launches Carrier as a detached run (its own session, its output in `<folder>/engine/logs/start.log`) and returns once the control socket answers, within 15 seconds; in a Carrier folder where Carrier already runs it says so, does nothing and exits 0, and if it does not come up it prints the end of the log and exits 1. `carrier stop`, `pause`, `resume` and `check` do from any terminal what the panel's `Ctrl+C`, `p` and `r` do (`check` looks at GitHub now) and confirm in one line: exit 0 on success (`stop` also when none runs), 1 when no Carrier runs (or answers), 2 when it speaks another protocol. See [ADR 0010](docs/adr/0010-carrier-runs-in-the-background-as-a-detached-process.md).
|
|
41
|
+
- **Status** — `carrier status` prints the panel of the running Carrier once, from any terminal; exit 0 it runs, 1 it is not running, 2 it speaks another protocol. `carrier watch` attaches the live panel to it with the same keys as the foreground one, except `q` (and `Ctrl+C`) only leave: Carrier keeps running. It exits 1 when none runs or when the Carrier it watches stops. Both show each epic's stage history (running, needing you, or ready for test), asked over the socket; a Carrier that does not list `sessions` in its hello is drawn from the snapshot alone, and an epic whose request fails is drawn without it.
|
|
42
|
+
- **Restart a stopped epic** — `carrier restart <owner/name>#<issue> [--author <option>] [--reviewer <option>]` resumes an epic in needs-human from the terminal (the `resumeEpic` request of the socket), on the agent option of each role you choose. In a terminal and without flags it asks for each role, with the current option preselected and the role that failed marked; without a terminal it keeps the current options. The same option continues the agent's session with `--resume`; another one starts a new session in the epic's worktree and pull request, rebuilt from SQLite and GitHub like a lost session. It prints one line and exits 0; a refusal prints the engine's reason and exits 1 (so does no Carrier running), a malformed argument or another protocol exits 2. It needs an engine that lists `agentOptions` in its hello.
|
|
43
|
+
- **Chat host** — `carrier chat --workspace <folder> [--agent <name>]` opens a conversation (it prints the answer as it streams; `/quit` leaves and the session stays open), `carrier chat --list [--archived]`, `--archive`, `--unarchive` and `--delete <id>` (deleting leaves nothing of the session; a working one is refused) talk to the chat host, a process of its own that keeps everything in `<folder>/chat/` (the Carrier folder, `~/.carrier` or `CARRIER_HOME`), and starts it detached when none answers. It needs no engine config: with nothing but the Carrier folder it starts, and its optional settings are in `<folder>/chat/chat.json` ([configuration](docs/configuration.md#chat-configuration)). It ends by itself a minute after the last client leaves, or at once with `carrier chat --stop`. The engine and the host run side by side and neither's stop touches the other. `carrier chat-host` runs it in the foreground. See [ADR 0013](docs/adr/0013-the-chat-host-is-its-own-process-with-an-adapter-per-provider.md) and [the spec](docs/specs/chat-host.md).
|
|
44
|
+
- **Config** — a user JSON file merged over defaults, validated with Zod at startup with one error per field; per-repo overrides for identity, agent option and persona; under separate identities (the default) author and reviewer must resolve to different profiles.
|
|
45
|
+
- **Polling and queue** — FIFO across repos with pagination, rate-limit and outage handling; one instance per Carrier folder (an exclusive SQLite lock released by the OS on death).
|
|
46
|
+
- **Scheduler** — atomic reservation, configurable concurrency, label management (`running`, `ready-for-test`, `needs-human` are created in each repo if missing).
|
|
47
|
+
- **Stages** — ship, review and fix loops with a typed caller result (`completed`, `changes_requested`, `needs_human`) imported idempotently in a single transaction.
|
|
48
|
+
- **Failure handling** — execution retries with session resume, per-execution timeouts (or `unlimited`), Forge authentication errors that stop only the affected profile, repos removed from config, and a stop rule when the final PR is merged or the epic is closed.
|
|
49
|
+
- **Agent runners** — real adapters for the Claude, Codex and Antigravity (`agy`) CLIs (cloud models, or local ones from LM Studio or Ollama through Claude and Codex) as child processes (session id, resume, output capture, stop, orphan detection), with per-execution credentials and the bot's git identity.
|
|
50
|
+
- **Workspaces** — managed clones in `<folder>/engine/repos/`, and a separate detached checkout for the reviewer in `<folder>/engine/reviews/`; an epic's worktree is found with git, wherever it is.
|
|
51
|
+
- **Terminal panel** — a dependency-free ANSI dashboard: uptime, repos watched and the next check, slots, a card per running epic (stage chain, review round, blockers, time in stage, last activity, links, errors), the queue, epics that need you and the ones ready for test, a hint when it is idle, and below it the epics that ended inside the finished window: DONE (merged) and CANCELLED (closed without a merge, with the stored reason), ten each and a `+N more` line; plain lines when there is no TTY. On a terminal it reads keys; a row is always selected (highlighted), each running card shows a `now` line of what its agent is doing (three lines when selected), and the footer lists only the keys that apply: `↑`/`↓` (or `j`/`k`) select, `enter` opens the PR or issue, `l` shows the agent's output, `r` checks GitHub now, `p` pauses or resumes taking new epics, `?` lists them, `q` quits like `Ctrl+C` (stops Carrier; in `watch` it only leaves).
|
|
52
|
+
- **Logging** — [`@ashulab/universal-logger`](https://github.com/AshuLab/universal-logger) to files, with token redaction, and 30-day retention of logs and sessions.
|
|
53
|
+
- **Tests** — unit tests with fakes and a temp SQLite; gated integration tests with real tools.
|
|
54
|
+
|
|
55
|
+
## Tech stack
|
|
56
|
+
|
|
57
|
+
| Layer | Technology |
|
|
58
|
+
|-------|-----------|
|
|
59
|
+
| Runtime | Node.js 22+ for the package (compiled to `dist/`); 26 to work on it, where TypeScript runs via native type stripping. ESM |
|
|
60
|
+
| Language | TypeScript (strict) |
|
|
61
|
+
| State | SQLite via `better-sqlite3`, schema and migrations with Drizzle ORM + Drizzle Kit |
|
|
62
|
+
| Validation | Zod (config, caller result, GitHub responses) |
|
|
63
|
+
| Logging | `@ashulab/universal-logger` |
|
|
64
|
+
| GitHub access | `gh` CLI, authenticated per profile through Agent Forge (GitHub Apps) |
|
|
65
|
+
| Identities | [Agent Forge](https://github.com/AshuLab/agent-forge) bot profiles, or your own `gh` login |
|
|
66
|
+
| Agents | Claude Code, Codex and Antigravity (`agy`) CLIs, headless; their models from the cloud or from LM Studio / Ollama |
|
|
67
|
+
| Workflow | [Solve](https://github.com/AshuLab/skills) skills (`ship`, `code-review`, ...) |
|
|
68
|
+
| Terminal UI | `@clack/prompts` for `init` and `doctor`; plain ANSI escape sequences for the panel |
|
|
69
|
+
| Testing | Vitest (unit and gated integration) |
|
|
70
|
+
| Package manager | pnpm 12 |
|
|
71
|
+
|
|
72
|
+
## Repository structure
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
carrier/
|
|
76
|
+
├── src/
|
|
77
|
+
│ ├── terminal/ # everything a terminal user sees; a web UI would be a sibling of this folder
|
|
78
|
+
│ │ ├── cli/ # the command line: args, commands/ (run, start, stop/pause/resume/check, status, watch, init, doctor, schema), the control-socket client, config file, the wiring of the real engine, Ctrl+C
|
|
79
|
+
│ │ ├── prompts/ # the terminal contract (`Ui`) and its clack implementation: prompts, spinners, check lines
|
|
80
|
+
│ │ ├── wizard/ # `carrier init`: the flow, what it asks (questions/), what it needs from the machine (deps), the recap
|
|
81
|
+
│ │ └── dashboard/ # the panel: renderers (dashboard, plain), keys and their state, agent-output tail, each epic's stage history
|
|
82
|
+
│ ├── status/ # what Carrier is doing right now, read from SQLite (the view any front-end draws)
|
|
83
|
+
│ ├── control/ # the control socket: snapshot, pause/resume, check, stop, sessions, events, epic, files, availableRepos (a page at a time), resumeEpic
|
|
84
|
+
│ ├── doctor/ # the checks of `carrier doctor` and the remedy for each failure
|
|
85
|
+
│ ├── config/ # config schema, defaults, loading/merging, per-repo resolution, JSON Schema, default path
|
|
86
|
+
│ ├── orchestration/ # engine: polling, scheduler, stages, retries, restart, final-PR rules, ports + fakes
|
|
87
|
+
│ ├── execution/ # AgentRunner/Workspaces ports, caller result parsing and idempotent import
|
|
88
|
+
│ ├── harnesses/ # Claude, Codex and Antigravity runners, credentials, local providers, orphan detection
|
|
89
|
+
│ ├── integrations/ # GitHub through Agent Forge (`gh` via execFile)
|
|
90
|
+
│ ├── prompts/ # author and reviewer prompts (what the agents are told, not terminal prompts)
|
|
91
|
+
│ ├── storage/ # SQLite schema, migrations, lock, executions, events, retention
|
|
92
|
+
│ ├── logging/ # universal-logger integration
|
|
93
|
+
│ └── format/ # duration formatting
|
|
94
|
+
├── macos/ # the native app: a client of the control socket (see its README)
|
|
95
|
+
└── docs/
|
|
96
|
+
├── specs/ # the specs the features were built from
|
|
97
|
+
├── adr/ # architecture decision records
|
|
98
|
+
├── research/ # spike findings
|
|
99
|
+
└── glossary.md # domain vocabulary
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Getting started
|
|
103
|
+
|
|
104
|
+
### 1. Prerequisites
|
|
105
|
+
|
|
106
|
+
- A Mac with [Homebrew](https://brew.sh). The install below brings Node.js 22 or newer, `git` and the GitHub CLI (`gh`) if they are missing. The app needs an Apple Silicon Mac on macOS 26 or later. Working from a clone needs Node.js 26 and pnpm 12.
|
|
107
|
+
- At least one of the `claude`, `codex` and `agy` (Antigravity) CLIs, installed and logged in from the terminal where you run Carrier. For local models, LM Studio or Ollama running.
|
|
108
|
+
- Two different GitHub identities, one per role: [Agent Forge](https://github.com/AshuLab/agent-forge) bot profiles, or your own `gh` login for one of them. Or, if you cannot get a second one, a **shared identity**: your `gh` login for both roles (`carrier init` offers it, or `--identities shared`). Both roles then act with everything that login's token can do.
|
|
109
|
+
- The target repos use the [Solve](https://github.com/AshuLab/skills) flow.
|
|
110
|
+
|
|
111
|
+
### 2. Install
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
brew install ashulab/tap/carrier
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That installs the `carrier` command (with `node`, `git` and `gh` if they are missing) and the Carrier app in `/Applications`. Open the app with a double click: it finds `carrier` by itself, so there is nothing to fill in. Use the full name: it trusts only that formula under Homebrew's tap trust ([ADR 0017](docs/adr/0017-carrier-is-installed-with-a-homebrew-tap-and-the-app-is-not-signed-with-a-developer-id.md)). The app is signed ad hoc, not notarized, and the cask clears the quarantine attribute for you; if macOS still refuses to open it, see [troubleshooting](docs/troubleshooting.md#macos-will-not-open-the-app).
|
|
118
|
+
|
|
119
|
+
Without Homebrew, the command alone installs with `pnpm add -g @ashulab/carrier` (or `pnpm dlx @ashulab/carrier` to run it without installing). To work on Carrier itself, clone the repo and run `pnpm install`; the app is built from the clone as [`macos/README.md`](macos/README.md) says.
|
|
120
|
+
|
|
121
|
+
#### Upgrade
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
brew upgrade carrier # the command: the engine and the chat host
|
|
125
|
+
brew upgrade --cask carrier # the app
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Both move to the same release. They can be upgraded at different moments, and when they speak different protocols the app says which one to update and does not connect until you do ([troubleshooting](docs/troubleshooting.md#the-app-asks-to-update-carrier-or-itself)).
|
|
129
|
+
|
|
130
|
+
#### Uninstall
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
brew uninstall --cask carrier # the app first: it depends on the command
|
|
134
|
+
brew uninstall carrier # the command
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This leaves `~/.carrier` (or your `CARRIER_HOME`), with your config, sessions and clones. Add `--zap` to the cask (`brew uninstall --zap --cask carrier`) to remove also the app's own preferences (`~/Library/Preferences/dev.ashulab.carrier.plist`), and nothing else.
|
|
138
|
+
|
|
139
|
+
To remove the Carrier folder as well, stop Carrier first (`carrier stop` and `carrier chat --stop`), then delete it by hand:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
rm -rf ~/.carrier
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
That deletes everything in it, for good: `config.json`; `engine/` (its `state/` with `carrier.db`, the control socket and the lock, its `logs/`, the managed clones in `repos/`, the reviewer's checkouts in `reviews/` and the epics' `worktrees/`); and `chat/` (its `state/` with `chat.db`, the chat socket and the lock, its `logs/`, the repositories it cloned in `projects/`, the session `worktrees/` and `chat.json`). Commit and push any work you want to keep from those worktrees first. See [Where it keeps things](#where-it-keeps-things).
|
|
146
|
+
|
|
147
|
+
### 3. Create the config
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
carrier init
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
It looks at the machine (stopping with the fix if `git`, `gh` or every agent CLI is missing, and listing the models your local servers have), asks for the repos to watch (picked from the repos your `gh` login can push to, grouped by owner), whether the author and the reviewer use separate identities or one shared identity (your `gh` login for both, after a note that both act with everything its token can do; it never reads Agent Forge), the identity of each role under separate identities, then an **Agent options** section before the roles, where you add agent options one at a time (the CLI among the installed ones, the local provider when a server is running, the model and, for Claude and Codex, the config folder: the `~/.claude`, `~/.claude-*` and `~/.codex` it finds with their login state, the environment, or a typed path; Antigravity has no folder), each with a suggested name that must be a lowercase word and unique, and after each one whether to add another; each role then asks one question, which of those options runs it, and with two or more options it asks whether to turn failover on and in which order (the `failover` block). Run again on an existing config it keeps the options and the `failover` block and lets you add more (editing or removing one is done in the file). Then the persona of each role (a name and a prompt, each skippable, and when editing `-` clears one; `--author-persona-name`, `--author-persona-prompt`, `--reviewer-persona-name` and `--reviewer-persona-prompt` answer them), writes the config to `~/.carrier/config.json` (one global file per user; change it with `--out`) and runs the checks below. Run again on an existing config, it offers to edit it with its answers as the starting points. Before saving it shows a recap to confirm, and the checks that follow print only what fails (otherwise a single count), then it shows how many epics are waiting and offers to start Carrier. If there is no second GitHub identity it can run `agent-forge add` for you on the spot. Every question has a flag (`carrier init --help`); `--author` and `--reviewer` (`<harness>:<model>`, with `--author-provider` and `--reviewer-provider`) each declare one option named after its harness and skip the section and the failover question when both are given. `--yes` never asks, so an agent can run it too (`--identities shared --yes` takes the `gh` login for both roles and prints the note; it cannot go with `--author-profile` or `--reviewer-profile`):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
carrier init --yes --repo AshuLab/my-repo \
|
|
157
|
+
--author-profile forge:<agent-1> --author claude:<model> \
|
|
158
|
+
--reviewer-profile forge:<agent-2> --reviewer codex:<model>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Without `--author` and `--reviewer`, `--yes` writes one `claude` option that runs both roles; the flags above give each role its own option.
|
|
162
|
+
|
|
163
|
+
Check a config (yours or a hand-written one) at any time:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
carrier doctor
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It verifies the tools are installed, that the author and the reviewer really are different identities (under separate identities), that each identity can get a token for each repo, and that the orchestrator can read it. `carrier schema` prints the JSON Schema of the config for your editor. `carrier doctor --json` prints the checks as a JSON array (`{ name, status, detail?, fix? }`, `status` `ok`, `warn` or `fail`) and nothing else, with the same exit code (1 when any fails); the macOS app's Health tab reads it.
|
|
170
|
+
|
|
171
|
+
#### The file, by hand
|
|
172
|
+
|
|
173
|
+
A JSON file merged over the defaults (objects merge by field, simple values replace, lists replace entirely). Invalid configs print every error with its field and exit without starting anything.
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"repos": [{ "name": "AshuLab/my-repo", "agents": { "reviewer": { "provider": "claude", "model": "<model>" } } }],
|
|
178
|
+
"profiles": {
|
|
179
|
+
"author-bot": { "kind": "forge", "agent": "<forge-agent-1>" },
|
|
180
|
+
"reviewer-bot": { "kind": "forge", "agent": "<forge-agent-2>" }
|
|
181
|
+
},
|
|
182
|
+
"orchestratorProfile": "author-bot",
|
|
183
|
+
"providers": {
|
|
184
|
+
"claude": { "harness": "claude" },
|
|
185
|
+
"codex": { "harness": "codex" }
|
|
186
|
+
},
|
|
187
|
+
"agents": {
|
|
188
|
+
"author": { "profile": "author-bot", "provider": "claude", "model": "<model>" },
|
|
189
|
+
"reviewer": { "profile": "reviewer-bot", "provider": "codex", "model": "<model>" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Defaults: `pollIntervalMinutes` 5, `logLevel` `info`, and `limits` of `maxConcurrentEpics` 3, `maxReviews` 5, `maxExecutionRetries` 3, `maxExecutionHours` 4 (or `"unlimited"`), `githubCallTimeoutSeconds` 30. Identity, provider, model and persona for author and reviewer can be overridden per repo; an AI provider (`providers`) is a harness, optional local server provider and optional config folder. The file holds no secrets: a profile is `{ "kind": "forge", "agent": ... }` or `{ "kind": "gh" }`.
|
|
195
|
+
|
|
196
|
+
### 4. Run it
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
carrier
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`carrier` reads `~/.carrier/config.json`; pass `--config <path>` to use another file. It holds the terminal and runs until `Ctrl+C`; to leave it running with the terminal closed, use `carrier start` instead (see [What's included](#whats-included)). Only one instance can run per Carrier folder. From a clone, run `pnpm build` and then `node dist/terminal/cli/main.js`, or `node src/terminal/cli/main.ts --config ...` (Node 26 runs the TypeScript directly).
|
|
203
|
+
|
|
204
|
+
Mark an epic `solve:epic:ready` in a configured repo and watch the panel.
|
|
205
|
+
|
|
206
|
+
## Scripts
|
|
207
|
+
|
|
208
|
+
| Command | What it does |
|
|
209
|
+
|---------|--------------|
|
|
210
|
+
| `pnpm build` | Compile to `dist/` (what gets published) |
|
|
211
|
+
| `pnpm typecheck` | Type-check the project |
|
|
212
|
+
| `pnpm test` | Full unit suite — fakes and a temp SQLite, no network and no real agents |
|
|
213
|
+
| `pnpm test:integration` | Integration tests with real tools; they skip with a clear message unless their environment variables are set |
|
|
214
|
+
| `pnpm db:generate` | Generate a Drizzle migration after changing the schema |
|
|
215
|
+
|
|
216
|
+
The gated tests use `CARRIER_IT_*` and `CARRIER_TEST_*` variables; each test file lists the ones it needs in its header.
|
|
217
|
+
|
|
218
|
+
## Where it keeps things
|
|
219
|
+
|
|
220
|
+
Carrier's folder is `~/.carrier/` (override it with `CARRIER_HOME`). The engine's config is `~/.carrier/config.json`, and the chat keeps everything it owns in `~/.carrier/chat/`:
|
|
221
|
+
|
|
222
|
+
- `chat/state/` — the chat host's `chat.db`, `chat.sock` and lock; `chat/logs/` — its logs; `chat/projects/` — the repositories it clones; `chat/worktrees/` — the worktrees of the sessions that ask for one; `chat/chat.json` — its optional settings ([configuration](docs/configuration.md#chat-configuration)).
|
|
223
|
+
|
|
224
|
+
The engine keeps its files in `~/.carrier/engine/`, in the same Carrier folder; only Carrier writes there, and `--config` changes which config file is read, never this folder:
|
|
225
|
+
|
|
226
|
+
- `engine/state/` — `carrier.db` (SQLite), `control.sock` and `carrier.lock`.
|
|
227
|
+
- `engine/logs/` — `orchestrator.log`, `executions/<id>/` with each agent's full output, and `start.log`. Logs and sessions of a finished or cancelled epic are deleted after 30 days; SQLite and worktrees are never touched.
|
|
228
|
+
- `engine/repos/<org>/<repo>/` — managed clones; `engine/reviews/<repo>/<epic>/` — the reviewer's separate checkout.
|
|
229
|
+
- `engine/worktrees/<repo>/<feature>/` — where Carrier keeps an epic's worktree by default; any other place works, since Carrier finds a worktree with git.
|
|
230
|
+
|
|
231
|
+
## Developing against a throwaway folder
|
|
232
|
+
|
|
233
|
+
Everything the chat owns is in the Carrier folder, so a development run can use a scratch one and never touch your real `~/.carrier`. Keep the path short: a Unix socket path is limited to about 100 bytes and the host refuses a longer one.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
export CARRIER_HOME=$(mktemp -d /tmp/carrier.XXXXXX) # the scratch folder
|
|
237
|
+
node src/terminal/cli/main.ts chat-host # the host in the foreground; Ctrl+C or `chat --stop` ends it
|
|
238
|
+
node src/terminal/cli/main.ts chat --list # from another terminal with the same CARRIER_HOME: "No chat sessions."
|
|
239
|
+
node src/terminal/cli/main.ts chat --stop # ends the host
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The host creates `$CARRIER_HOME/chat/` (`state/`, `logs/`) on its own and uses the defaults of `chat.json`; to try other settings, write `$CARRIER_HOME/chat/chat.json` before starting it (a wrong file stops the host and names the key). To run the app against it, build it (see [`macos/README.md`](macos/README.md)) and start its binary from the same terminal, so it inherits `CARRIER_HOME`:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
(cd macos && ./build-app.sh)
|
|
246
|
+
macos/Carrier.app/Contents/MacOS/Carrier # opens the window; with `carrier` found, starts the host in $CARRIER_HOME/chat/ if none answers
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The app finds `carrier` by itself (the Homebrew one, `/opt/homebrew/bin/carrier`) and says in the Sessions section when it does not. To test your clone's `carrier` instead, run `pnpm build` and put the path of `dist/terminal/cli/main.js` (or of `src/terminal/cli/main.ts`) in the advanced "Use this carrier" setting of Settings > Engine; a path ending in `.ts` or `.js` runs under `node`, which the app finds from your login shell. That setting is the app's preference, not part of the folder. The app replaces a running host only when the installed `carrier` has a newer version, and a build under development keeps the same one, so stop the host with `carrier chat --stop` after changing the code. When you are done, `carrier chat --stop`, clear the setting and delete the folder.
|
|
250
|
+
|
|
251
|
+
## Security
|
|
252
|
+
|
|
253
|
+
Unattended agents run with **your** permissions: Claude with permissions bypassed, Codex with `--dangerously-bypass-approvals-and-sandbox` and Antigravity with `--dangerously-skip-permissions` (a model served locally changes nothing about this), so they can touch files, credentials and other repos on the machine. Carrier adds no isolation in this version ([ADR 0004](docs/adr/0004-agents-run-on-host-with-terminal-preferences.md)); run it on a machine or user account where you accept that. Each execution's GitHub token has the minimum scopes of its role, and your repos should have branch protection. Without a container, stopping an agent does not guarantee that its child processes die. Hardening unattended agents is planned follow-up work.
|
|
254
|
+
|
|
255
|
+
## Documentation
|
|
256
|
+
|
|
257
|
+
- Using it: [configuration](docs/configuration.md), [preparing a repo](docs/preparing-a-repo.md) and [troubleshooting](docs/troubleshooting.md).
|
|
258
|
+
- [Specs](docs/specs/) (the orchestrator's is [autonomous-orchestrator](docs/specs/autonomous-orchestrator.md)), [glossary](docs/glossary.md) and the [macOS app](macos/README.md).
|
|
259
|
+
- [ADRs](docs/adr/) — coordination vs. evaluation, SQLite as the authority, agents on the host (which replaces the container ADR 0003), the engine independent of the macOS app, and Carrier running detached on demand, not as a login service.
|
|
260
|
+
- [Research](docs/research/) — headless agents, Antigravity, Agent Forge, logging and panel, containers (dropped, kept as reference).
|
|
261
|
+
- [`AGENTS.md`](AGENTS.md) — entry point for coding agents working in this repo.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
[MIT](LICENSE)
|