Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,12 @@
"message": "Repository primitives must not depend on tracker code."
},
{
"regex": "^\\.\\./(?:\\.\\./)*(?:linear(?:/|-)|inngest(?:/|$)|triage(?:/|$)|spec(?:/|$)|implementation(?:/|$))",
"message": "Repository primitives own isolated execution and publication, not tracker or domain policy."
"regex": "^@octokit(?:/|$)",
"message": "Repository workspace primitives must not own GitHub publication."
},
{
"regex": "^\\.\\./(?:\\.\\./)*(?:linear(?:/|-)|inngest(?:/|$)|triage(?:/|$)|spec(?:/|$)|implementation(?:/|$)|github(?:/|$)|providers(?:/|$))",
"message": "Repository primitives own isolated execution and cleanup, not tracker, publication, provider, or domain policy."
}
]
}
Expand Down
15 changes: 12 additions & 3 deletions Dockerfile.linear-worker
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,28 @@ RUN pnpm build && pnpm prune --prod --ignore-scripts
FROM node:24.18.0-bookworm-slim@sha256:6f7b03f7c2c8e2e784dcf9295400527b9b1270fd37b7e9a7285cf83b6951452d AS runtime

ENV CODEX_HOME=/home/node/.codex
ENV COREPACK_HOME=/opt/corepack
ENV HOME=/home/node
ENV NODE_ENV=production
ENV PATH=/app/node_modules/.bin:${PATH}
WORKDIR /app

# Git is required by Harness's workspace guard and by Codex repository discovery.
# Git is required by Harness's workspace guard, Grove, and Codex repository discovery.
# hadolint ignore=DL3008
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates git \
&& rm -rf /var/lib/apt/lists/* \
&& corepack enable \
&& corepack install --global pnpm@11.9.0 \
&& git config --system --add safe.directory /workspace \
&& mkdir -p /home/node/.codex \
&& chown node:node /home/node/.codex
&& mkdir -p \
/home/node/.cache/pnpm \
/home/node/.codex \
/home/node/.harness/repositories \
&& chown -R node:node \
/home/node/.cache \
/home/node/.codex \
/home/node/.harness

COPY --from=build --chown=node:node /build/package.json ./package.json
COPY --from=build --chown=node:node /build/node_modules ./node_modules
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ The worker uses stable IDs and its triage profile from the target repository's
[Linear automation guide](docs/contributing/linear-automation.md) for the
Compose setup, health checks, and smoke tests.

Write-capable consumers use the standalone Grove-backed repository primitive.
It leases reusable writable worktrees, reruns repository setup against warm
ignored dependencies, and resets completed work without owning commits, pull
requests, or Linear policy.

## Configure Agents

`harness.json` stores target-repository defaults. `harness init` starts with the
Expand Down
7 changes: 7 additions & 0 deletions compose.linear-automation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ services:
environment:
CODEX_API_KEY: ${CODEX_API_KEY:-}
CODEX_HOME: /home/node/.codex
COREPACK_HOME: /opt/corepack
HARNESS_REPOSITORY_ROOT: /home/node/.harness/repositories
HARNESS_WORKER_HOST: 0.0.0.0
HARNESS_WORKER_PORT: "8080"
HOME: /home/node
Expand All @@ -47,6 +49,7 @@ services:
INNGEST_EVENT_KEY: ${INNGEST_EVENT_KEY:?set INNGEST_EVENT_KEY}
INNGEST_SIGNING_KEY: ${INNGEST_SIGNING_KEY:?set INNGEST_SIGNING_KEY}
LINEAR_API_KEY: ${LINEAR_API_KEY:?set LINEAR_API_KEY}
PNPM_CONFIG_STORE_DIR: /home/node/.cache/pnpm/store
init: true
networks:
- linear-automation
Expand All @@ -60,10 +63,14 @@ services:
target: /workspace
read_only: true
- codex-home:/home/node/.codex
- package-manager-cache:/home/node/.cache/pnpm
- repository-data:/home/node/.harness/repositories

networks:
linear-automation:

volumes:
codex-home:
inngest-data:
package-manager-cache:
repository-data:
17 changes: 17 additions & 0 deletions docs/contributing/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ real consumers expose the same stable contract.
| `lib/linear/` | Standalone, JSON-safe Linear read, write, pagination, and webhook primitives without domain or delivery policy |
| `lib/triage/` | Triage prompt, structured decision schema, and provider-independent operation |
| `lib/spec/` | Spec prompt, structured result schema, issue-key artifact validation, and provider-independent operation |
| `lib/repository/` | Grove-backed writable repository leases, safe setup, change inspection, and reset cleanup |
| `lib/linear-automation/` | Linear readiness policy, application event contracts, Inngest functions, worker config, and process hosting |
| `lib/skills/` | Packaged-skill installation support |
| `skills/` | Packaged skills installed into target repositories |
Expand Down Expand Up @@ -185,6 +186,20 @@ Workflows and operations depend on the shared provider interface. Provider
auth, SDK or CLI invocation, streaming, session continuation, model policy, and
sandbox details stay under `providers/` or provider-scoped configuration.

### Repository boundary

The repository module adapts Grove behind plain `RepositoryBase` and
`RepositoryRun` handles. It resolves mutable base refs to exact commits, gives
each durable work ID a writable branch lease, runs the target repository's
configured setup command after acquisition, reports plain Git changes, and
returns successful worktrees to a reusable pool.

Setup runs with a fixed environment allowlist, so Linear, Inngest, Codex, and
GitHub credentials are not passed to target-repository scripts. Reset cleanup
removes tracked and ordinary untracked work while retaining ignored dependency
caches such as `node_modules`. The module does not commit, push, open pull
requests, update Linear, or choose Spec and implementation policy.

### Delivery boundary

Inngest functions reload external truth before deciding or projecting. Event
Expand All @@ -210,6 +225,8 @@ check the runtime schema, exported schema, prompt, and consumer together.
| Self-hosted Inngest SQLite volume | Local delivery history, retry state, function metadata, and traces |
| Protected worker environment file outside a repo | Linear, Inngest, and optional Codex credentials for one deployment |
| Dedicated worker Codex credential volume | Optional unattended ChatGPT-backed Codex login, separate from the host account |
| Repository data volume | Writable controller clones, Grove leases, worktrees, and warm ignored dependencies |
| Package-manager cache volume | Reusable package downloads shared by setup runs for one Compose deployment |

Review artifacts are ignored workspace-local state. Self-hosted deployment
state and credentials are external user data. Neither belongs in Git.
Expand Down
43 changes: 32 additions & 11 deletions docs/contributing/linear-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@ spec needing revision returns as Open + Spec.
## Configure the target repository

The target repository's `harness.json` owns stable team, project, state, and
Agent action label IDs plus the triage execution profile. It contains no
secrets.
Agent action label IDs, the triage execution profile, and repository-run setup.
It contains no secrets.

```json
{
"repositoryRuns": {
"remote": "https://github.com/example/project.git",
"maxTrees": 2,
"setup": {
"command": ["pnpm", "install", "--frozen-lockfile", "--prefer-offline"],
"timeoutMs": 900000
}
},
"linearAutomation": {
"readiness": {
"teamId": "team-id",
Expand Down Expand Up @@ -98,8 +106,16 @@ worker can reuse it without adding a shared scheduler or project registry.

The deployment contains one self-hosted Inngest service and one Harness worker.
It is intentionally scoped to one configured target repository. Inngest keeps
its SQLite database in a named volume, while the worker reads the target checkout
through a read-only bind mount.
its SQLite database in a named volume, while the worker reads the target
checkout through a read-only bind mount.

Write-capable operations use a separate persistent repository-data volume. It
contains a writable controller clone plus Grove's reusable worktrees; the
read-only source bind is never fetched, checked out, or cleaned. Grove reset
keeps ignored `node_modules`, and a separate package-manager-cache volume keeps
downloaded packages. The setup command runs after every acquisition so those
warm dependencies are reconciled with the checked-out lockfile before an agent
receives the workspace.

Keep deployment secrets outside the target repository. The triage agent can read
the workspace, so an ignored file inside that workspace is not a safe secret
Expand Down Expand Up @@ -169,15 +185,16 @@ docker compose --env-file "$LINEAR_AUTOMATION_ENV" --file "$HARNESS_ROOT/compose
# Follow logs
docker compose --env-file "$LINEAR_AUTOMATION_ENV" --file "$HARNESS_ROOT/compose.linear-automation.yaml" logs --follow

# Stop containers while preserving SQLite and Codex credentials
# Stop containers while preserving SQLite, repository data, caches, and Codex credentials
docker compose --env-file "$LINEAR_AUTOMATION_ENV" --file "$HARNESS_ROOT/compose.linear-automation.yaml" down
```

Do not add `--volumes` to normal shutdown. It deliberately deletes Inngest
history and the dedicated Codex login. Both services use restart policies, and
the Connect worker automatically reconnects after an Inngest restart. The
worker's stop grace period is longer than the configured maximum triage runtime
so an active agent step can drain.
history, repository leases and warm dependencies, package-manager caches, and
the dedicated Codex login. Both services use restart policies, and the Connect
worker automatically reconnects after an Inngest restart. The worker's stop
grace period is longer than the configured maximum triage runtime so an active
agent step can drain.

To run another target project, create another environment file with a distinct
`COMPOSE_PROJECT_NAME`, workspace path, and dashboard port. Keep one configured
Expand Down Expand Up @@ -216,5 +233,9 @@ SQLite state on success. It does not call live Linear or a real model.
`make smoke-linear-automation-compose` is the explicit Docker packaging smoke.
It validates and builds the Compose model, starts both containers on a blocked-
egress smoke network, checks service health, restarts each service, proves the
worker reconnects and accepted event history survives, then removes all
disposable containers and volumes. It also does not call live Linear or a model.
worker reconnects and accepted event history survives, and runs a real Grove
repository lease across worker restart. The repository probe verifies
post-acquire setup, secret-free setup environment, dirty-work recovery, change
inspection, reset cleanup, warm ignored dependency reuse, and persistent named
volumes. The smoke then removes all disposable containers and volumes. It does
not call live Linear, GitHub, or a model.
2 changes: 1 addition & 1 deletion docs/contributing/script-command-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and [Setup Manifest](./setup-manifest.md) for generated artifacts and auth.
| Source CLI | `bin/harness.ts` | `harness init`, `harness linear worker`, `harness run change-review`, `harness run plan-review`, `harness runs prune`, `harness models`, `harness skills install` | User-facing reviews, the persistent Linear automation worker, run cleanup, model discovery, and skill installation. |
| Distribution smoke | `scripts/smoke-dist.ts` | `pnpm smoke:dist`, `make smoke-dist` | Verify built CLI behavior, init shim creation, skills install, dry-run review metadata, and handoff artifacts. |
| Linear automation smoke | `scripts/smoke-linear-automation.ts` | `pnpm smoke:linear-automation`, `make smoke-linear-automation` | Verify self-hosted Inngest startup, Connect registration, polling, revision routing, triage, and projection through fake boundaries. |
| Linear Compose smoke | `scripts/smoke-linear-automation-compose.ts` | `pnpm smoke:linear-automation-compose`, `make smoke-linear-automation-compose` | Verify the worker image and self-hosted Compose packaging, health, restart, reconnection, and persistence boundaries without live traffic. |
| Linear Compose smoke | `scripts/smoke-linear-automation-compose.ts` | `pnpm smoke:linear-automation-compose`, `make smoke-linear-automation-compose` | Verify the worker image, self-hosted Compose packaging, health, restart, reconnection, real Grove lease recovery, warm reuse, and persistence without live traffic. |
| Codex request proxy | `scripts/codex-proxy.mjs` | `pnpm codex:proxy` | Inspect local Codex Responses API requests, request byte contributors, tool definition size, and reported input-token usage. |
| User install shim | `install` | `harness ...` from the installed user-level shim | Install or refresh the user command, usually under `~/.local/bin/harness`. |
| Target-repo shim | `harness init` | `.harness/bin/harness ...` inside the target repo | Pin a target repo to the Harness checkout that initialized it. |
Expand Down
Loading