Skip to content
Open
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
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ factory
the SPA), `bun:sqlite` for persistence
- Effect v4 (`4.0.0-rc.*`) for the run lifecycle and scheduling; Effect Schema is the schema
language throughout — workflow inputs, agent structured output, config.
- `@tanstack/ai` with `-opencode` and `-sandbox-local-process` for the agent runtime.
- agent runtime: one ACP adapter (`@agentclientprotocol/sdk`) driving `opencode acp` and Claude Code
(`@agentclientprotocol/claude-agent-acp`); `@tanstack/ai-acp` turns ACP updates into AG-UI chunks
(ADR 0013)
- React 19 SPA: tanstack router and query, tailwind v4, shadcn primitives
- oxfmt, oxlint (type-aware, with the `@effect/tsgo` and react rules)
- playwright for the browser legs
Expand All @@ -48,4 +50,7 @@ factory
- a prod instance of factory may be running on this machine (`bun src/cli serve --port 3005`,
log at `/tmp/factory-serve.log`)
- we use factory to build factory
- when doing real agent tests via opencode the preferred model is `opencode-go/big-pickle`
- real agent tests: opencode on `opencode/big-pickle` (free), Claude on `haiku` (cheap) or `sonnet`.
Model ids are the agent's own (`opencode/big-pickle`, not `opencode-go/big-pickle`). Live legs run a
scratch daemon on a spare port (3005 is prod) over a local bare origin and a fake `gh`, so
write-back stays local (`docs/findings/14-acp-live-leg.md` has the setup)
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,12 @@ bun add -d @frebreco/factory
```

**Bun ≥ 1.4.1 is required** — factory ships TypeScript and runs on Bun. You
also need the [`opencode`](https://opencode.ai) CLI, logged in (that is the
agent doing the work), and [`gh`](https://cli.github.com), authenticated (that
is what opens the pull requests).
also need a coding agent, logged in on this machine (that is what does the
work): **Claude Code** (a Claude login via the `claude` CLI, or
`ANTHROPIC_API_KEY`; factory bundles the agent itself) or
[**opencode**](https://opencode.ai) (the CLI on `PATH`, with a provider logged
in). And [`gh`](https://cli.github.com), authenticated (that is what opens the
pull requests). `factory serve` names whatever is missing when it starts.

### 2. Initialise

Expand Down Expand Up @@ -116,7 +119,7 @@ import { defineWorkflow, Schema } from "@frebreco/factory";
export default defineWorkflow("my-workflow", {
input: Schema.Struct({ topic: Schema.String }),
output: Schema.Struct({ prUrl: Schema.NullOr(Schema.String) }),
agent: { model: "opencode-go/glm-5.3-flash" },
agent: { agent: "opencode", model: "opencode/big-pickle" },

run: async (ctx, input) => {
/* ... */
Expand All @@ -129,7 +132,7 @@ export default defineWorkflow("my-workflow", {
| `id` | The name you start it by — in the UI's picker and in `factory start <id>` |
| `input` | A schema. It generates the UI's form and validates `--input` **before** the run starts |
| `output` | Optional. What `run` resolves to, recorded in the run's history |
| `agent` | Optional defaults for every agent step — mainly `model` |
| `agent` | Optional defaults for every agent step: `agent` (`claude` / `opencode`) and `model` |
| `run` | Your workflow: `async (ctx, input) => output` |

### The `ctx` you get
Expand Down Expand Up @@ -158,6 +161,18 @@ meta.output; // { title, body } | undefined
meta.finalText; // the raw text, always
```

### Choosing the agent and model

`agent` and `model` can be named on the `ctx.agent` call, the run
(`factory start --agent/--model`, a schedule's `agent`), the workflow, and the
config (`agent.default`, `agent.models`). The most specific level that names an
agent wins. The model comes from the most specific level that names one, at or
above the level that chose the agent, and otherwise from `agent.models[agent]`.
A model never carries across to another agent. Ids are the agent's own (`haiku`,
`sonnet`, `opus`; `opencode/big-pickle`), passed verbatim. Factory always sends
one, and a step with no model, or with one the agent doesn't offer, fails
before the prompt is sent.

### Registering it

A workflow exists because it is imported into your config. There is no
Expand Down Expand Up @@ -191,6 +206,10 @@ export default defineConfig({
identity: { name: "Factory", email: "factory@acme.example" },
},
workflows: [hello],
agent: {
default: "claude",
models: { claude: "sonnet", opencode: "opencode/big-pickle" },
},
maxConcurrentRuns: 2,
retainedWorkspaces: 10,
});
Expand All @@ -200,6 +219,9 @@ export default defineConfig({
| -------------------- | -------------------- | ------------------------------------------------------------------------------------ |
| `repo` | — | The repository runs clone from, and where write-back opens PRs |
| `workflows` | — | The registry. Imported = available |
| `agent.default` | `"opencode"` | The agent a step runs on unless something more specific names one |
| `agent.models` | `{}` | Each agent's model unless something more specific names one. No built-in model |
| `agent.hostSettings` | `"ignore"` per agent | `"include"` lets runs see your own `~/.claude` / `~/.config/opencode` settings |
| `maxConcurrentRuns` | `3` | Runs allowed in flight at once. Over the limit is **refused, not queued** |
| `retainedWorkspaces` | `10` | Finished working trees kept for inspection, oldest evicted first. Must be ≥ the limit |
| `workspaceRoot` | `.factory/workspaces`| Where per-run working trees live |
Expand All @@ -216,6 +238,7 @@ your workflows are not: commit those.
factory init [--dir <path>] [--force] # scaffold .factory/ (never overwrites without --force)
factory serve [--port <n>] [--db <path>] # the daemon: HTTP API, SSE, and the web UI
factory start <workflowId> --input <json> [--watch] [--url <base-url>]
[--agent claude|opencode] [--model <id>]
factory runs # every run this project has recorded
factory log <runId> # replay one run's full history
```
Expand Down Expand Up @@ -283,13 +306,14 @@ simply serves the API and the UI — nothing runs unless you ask for it.
Factory is a proof of concept, and it is honest about what it is not:

- **There is no sandbox isolation.** Agents run as your user, on your machine,
with your `gh` and `opencode` credentials, in a clone of your repository.
Point it at repositories and issues you trust.
with your `gh`, Claude and opencode credentials, in a clone of your
repository, and every permission they ask for is granted. Point it at
repositories and issues you trust.
- **Workflow inputs reach the agent's prompt.** Anyone who can reach the daemon
can start a run, so do not expose the port beyond your machine, and never
interpolate an input straight into `ctx.exec`.
- **It needs your machine's logins.** There is no credential injection yet:
`opencode` and `gh` must be authenticated on the host.
`gh` and the agents you use must be authenticated on the host.

---

Expand Down
5 changes: 5 additions & 0 deletions docs/adr/0007-opencode-serve-per-run-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Status

**Superseded 2026-10-02 by [ADR 0013](./0013-agents-over-acp.md).** Agents now speak ACP over
stdio, one process per step, so there is no `opencode serve` and no port to race for. The
free-port probe was deleted with the opencode adapter (#66). Kept as the record of why the race
existed.

Accepted (2026-09-15). Live-leg-proven, not pre-implementation: the failure it fixes recurred
deterministically in phase 5's P6 concurrency leg before it, and the retried leg after the fix
passed clean (`docs/findings/11-phase5-live-leg.md`, finding L1).
Expand Down
10 changes: 10 additions & 0 deletions docs/adr/0012-agent-runtime-seam.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Status

**Amended 2026-10-02 by [ADR 0013](./0013-agents-over-acp.md):**

- **§3** has no job left. Permission asks are answered by the ACP adapter's callback, so
`AgentAdapter.prepareWorkspace` and `StartRunOptions.prepareWorkspace` are deleted (#66).
- **§6** is overtaken. There is no sandbox provider: the agent runs on the host in the run's tree
over stdio.

§1, §2, §4 and §5 stand. §2's rule held as written: the `AgentSignal` union grew `usage` because
factory gained fields for context and cost.

Accepted, 2026-09-18. Decided **ahead of the implementation**, deliberately: this seam decides what
several tickets in the Effect epic are allowed to move, so it has to exist before the code does.
Unlike ADR 0001, nothing here is distilled from a spike — it is a boundary statement, and the
Expand Down
Loading
Loading