Skip to content
Ronin11Public

About

A Software Factory

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Forge

Forge is an unattended software factory. You give it a task, in plain language, against a git repository; it clones the repository into a sandboxed worktree, runs a coding agent there, and verifies what came back against the repository's own checks and a set of hidden tests the agent never sees. A verified task is landed on the base branch, deployed if the repository has a deploy target, and every step is recorded so it can be measured. Jobs run a product's own automations, such as a webhook or a scheduled rule, through the same kernel: an agent or a plain command, verified and recorded the same way.

task --> sandboxed attempt --> verify (repo checks, then hidden tests) --> land --> deploy
                                    |
                                    `-- a failed row retries with that failure as feedback

Contributing covers development and checks; Security describes the isolation boundary and reporting problems.

Quickstart

Prerequisites: Rust, git, and Linux with bubblewrap for a sandbox (bwrap on PATH, with unprivileged user namespaces allowed; 0.10 or later, older ones run attempts with cold package caches). macOS (aarch64) runs unsandboxed: without bwrap, attempts run on the host backend, with no egress bound and no private home, and forge doctor warns so. And the claude CLI, logged in (Forge runs it as the agent; codex and copilot are providers you add in config.toml). A systemd user session is optional: without one, forge init prints the commands to run the worker and web client by hand.

cargo build --release --workspace            # the kernel and, beside it, forge-web, forge-tui, forge-test, forge-repomap
export PATH="$PWD/target/release:$PATH"      # or symlink the binaries somewhere already on PATH
forge init                                   # set up FORGE_HOME: config, workflow catalog, web.token, units
forge doctor                                 # check this machine can run attempts (forge init ends with this too)
forge demo                                   # see a real task land on a scratch repo (--fake: no agent, no cost)

A bare cargo build --release builds the kernel alone, and forge init then writes a web unit pointing at a forge-web that is not there.

forge init [--home DIR] creates FORGE_HOME (default ~/.local/share/forge), writes the operator's config.toml template if none exists, commits the built-in workflow catalog into FORGE_HOME/workflows as its own git repository, generates web.token, and — when a systemd user session is reachable — installs and enables forge-worker and forge-web under ~/.config/systemd/user with linger, so they survive a logout (without one, it prints the commands to run by hand instead). It is safe to run again: an already-initialized machine reports nothing changed. See docs/OPS.md, "Installing and upgrading".

Register a repository by giving it a forge.toml that declares [checks] (see this repository's own forge.toml for an example, and docs/CHECKS.md for the reference). Then:

forge add <repo> "<task>"                    # queue a task
forge work --once                            # drain the queue and exit
forge web serve                              # run the web client (forge-web)
forge web open                               # open the web client on this task's queue

Commands

forge run  <repo> "<task>" [--workflow W] [--check CMD]...  # run one task now
forge add  <repo> "<task>" [--workflow W] [--check CMD]...  # queue a task for `forge work`
forge work [--jobs N] [--poll SECS] [--once]                # run queued tasks: stay up and poll, or drain and exit with --once
forge log                                                   # list tasks, newest first
forge retry <id>                                            # re-queue a finished task as a new one: same text, workflow, budget, flags, and dependencies
forge answer <id> "<text>"                                  # answer a task blocked on a question and re-queue it as a retry
forge decisions                                             # list recorded operator answers, newest first
forge show <id>                                             # show one task and its attempts
forge supervise <id>                                        # run the supervisor on a task blocked with a question, now
forge doctor                                                # check this machine can run attempts and nothing is stuck
forge version                                               # print the crate version and, if built from a git checkout, its commit
forge workflows                                             # list the workflows a task can run, with declared metadata and measured outcomes
forge trace <id>                                            # everything about one task: every step's inputs, outputs, verdict rows, and a diagnosis
forge requests                                              # blocked tasks: questions for the operator and workflow requests
forge audit [--since 24h]                                   # every task lineage since the time, by outcome, with dangling tips named
forge stats                                                 # outcomes per workflow version and per step
forge events                                                # the event log as JSON lines: a client's subscription
forge snapshot                                              # tasks, requests, the worker, and the event offset to subscribe from, as one JSON object
forge land <id>                                             # land an already-verified task's branch through the integrator: merge the base in, re-verify, push, fast-forward
forge integrate <id>...                                     # merge verified tasks' branches together, clean ones first, re-verifying after each, without landing; one that would conflict is blocked naming the files
forge journal <id>                                          # what every earlier attempt in a task's piece of work said it did, and what the kernel found
forge gc [--dry-run]                                        # remove worktrees that are clean and whose commits are all on a remote

What happens to a task

  1. Create. forge.toml in the repo declares [checks] (argv arrays) and optional [defaults]: base_branch, remote (default origin), push (default true), check_timeout_secs (default 600). A task may add its own acceptance commands with --check. A task nothing would verify, no repo checks and no --check, is refused at creation.

  2. Clone. A single-branch clone of the base branch on forge/<id>-<slug>, with no remote: the agent inside cannot fetch anything, and the registered checkout and its .git are never mounted in the sandbox. Forge pushes by URL. The checks are then read from the base commit, never from the branch under test.

  3. Agent. claude --print --output-format stream-json --json-schema … in the worktree under bubblewrap: read-only system, private /run and /proc, a private disk-backed /tmp beside the worktree (removed with it), a tmpfs $HOME holding only the worktree, the repo's .git, the agent binary, and a private copy of the claude, codex and copilot CLIs' credentials and settings — seeded from the operator's real state and discarded with the attempt, so no attempt ever reads or writes the operator's actual session, and one attempt's session is never visible to the next. Its network is its own: a namespace with only loopback, and one route out, an allowlist proxy that lets through the model endpoint and the hosts the repository's forge.toml declares under [sandbox] egress and refuses the rest (src/egress.rs; forge doctor reports the policy). Killed at --timeout-secs (default 1800); --max-turns (default 100) is the other cliff. The raw stream is the attempt's log, prompt first.

    The schema makes the agent's final result structured, never parsed out of prose: summary, checks_run[] (only checks it actually ran, with the real outcome), claims[] each with evidence, and needs_input when it cannot proceed without the operator. All of it is a claim; step 4 compares it with what Forge measured. The stream's rate-limit frames (five-hour and seven-day utilization) are recorded per attempt: on subscription billing that, not the notional dollar figure, is the real budget.

  4. Verify. Three levels, each run by Forge after the agent exits, each a row in the attempt's verdict. A level runs only if the one before it passed.

    • L0 consistency with git: a structured result exists, clean tree, at least one commit, forge.toml untouched, every claim has evidence. The verifier derives the stored changes[] from git for every provider; it does not compare a model-reported file list against git. Since dca182f, the result schema no longer asks the model for changes[]. A needs_input question ends the task here with the question as its reason; retrying cannot answer it.
    • L1 the repo's declared checks, in the sandbox, each under check_timeout_secs. Then the claim rule, one-directional: a check the agent reported as passed that Forge could not reproduce is a claim:<name> row that fails. The agent may be conservative, never optimistic. Failing test names are extracted from go test, pytest, and jest output and fed to the retry by name.
    • L2 the task's --check commands, same treatment.

    A check's exit decides it. Its process group is killed after it exits and the output drained with a short grace, so a check that backgrounds a server cannot hold the attempt open.

  5. Retry. On failure the next attempt is told exactly which rows failed and their output (--retries, default 1), on the same branch. An attempt that hit its turn cap with work in hand is resumed in the same CLI session instead of starting over. A run the provider refused for a rate window is not an attempt: Forge waits for the reset and goes again. A task stops early when its cost reaches its cap. forge retry <id> can override --max-turns and --timeout-secs for the new task (default: as before). --max-turns (default 100) is only a runaway guard against a stuck attempt; the cost cap, the --timeout-secs wall-clock limit, and the provider's rate windows are the real bounds on how much an attempt can do.

  6. Land. On a verified success the kernel brings the base branch in as it is now on the remote, re-verifies the merged tree with every hidden suite, pushes the branch by explicit refspec, and fast-forwards the base; one landing at a time per repository. A conflict or a check that fails only with the base merged goes back to the coder as a retry. --no-land leaves the verified branch pushed for a human. See docs/ACTIONS.md, "Landing". A task queued --after another is claimed only once that one has landed, and is blocked with the reason if it fails, so a chain can be queued in one go. A dependency that blocks on a question keeps its dependents waiting, and any retry of a dependency (forge retry, forge answer, the supervisor) carries them along to the new task. --after (and an initiative file's after: header) may name a task in a different repository: a dependency means only "wait for that task to land (or succeed, if it never will)", which does not care which repository either task is in.

  7. Record. Every attempt is a row: agent exit, timeout, turns, tool calls, cost from the CLI's accounting, wall time from Forge's clock, commits and files from git, and the verdict. The agent's text is stored as text.

Forge also ends an attempt itself when two signs of going nowhere trip at once, as the tool calls stream: thirty calls without an edit, fifteen edits without a commit, or one command run five times. The session is kept and resumed with a prompt that names the signs and says what to do about each, so the work the agent located is not lost and the cap never has to arrive. Read-only steps (review, investigate) are never faulted for not editing.

Task states: queued, running, succeeded, failed (with the reason), unverified (nothing verified the work, or only the reviewer failed; pushed in the latter case), blocked (the agent asked a question or for another workflow). Attempt states: succeeded, checks_failed, agent_failed, unverified.

forge retry <id> re-queues a finished task as a new one: same text, workflow, budget, and flags, on a fresh branch; everything that had been queued --after it now waits on the new task instead, and anything that had been blocked by its failure is queued again (--chain is accepted and no longer needed). A task that already has a live descendant (a retry or a review demotion's follow-up still queued, running or unverified) is not retried: the refusal names it, and --again retries anyway; forge show lists a task's live descendants. forge answer <id> <text> answers a task blocked on a question (state blocked with its last attempt needs_input; anything else is refused, naming why): it records the question and answer in a decisions table and re-queues the task through the same retry path (retry_of the answered task, same settings and dependencies), with the task text becoming the original text, a blank line, and "Operator's answer to a question from an earlier attempt: <text>". forge decisions [--repo <path>] [--json] lists every recorded answer, newest first, with the task id, the question, and the answer. forge journal <id> prints what every earlier attempt in a task's piece of work said it did and what the kernel found; that same text is fed to each new attempt unless the task was queued --no-journal, the control arm of a measurement. forge stats groups outcomes by workflow hash and by step; --tools shows what the agents actually ran there instead — tool and shell calls with their time, and files read, aggregated per step.

The worker

forge work claims the oldest queued task, runs it, and repeats. --jobs N runs N at once with output prefixed by task id. When the queue is empty it polls every --poll seconds (default 30); --once drains and exits, for a timer. Every error is classified: a task fault fails that task with the reason and the loop continues; an environment fault (no disk, no bwrap, a broken store) puts the task back in the queue and stops the worker, so a broken machine never marks a queue of tasks failed. Tasks left running by a worker that died are requeued at the next start and resume at the following attempt number.

Signals: the first SIGINT or SIGTERM stops claiming and lets running attempts finish. A second aborts them, the sandbox tree dies with the child, and their tasks go back in the queue.

Budgets live in <FORGE_HOME>/config.toml, written with defaults on first use. per_task_usd stops a task's retries; per_day_usd stops the worker claiming once the rolling 24-hour spend reaches it. --budget overrides the task cap for one task.

Before each task or job claim, the worker checks free space on FORGE_HOME. [worker] min_free_gb in config.toml defaults to 50 GiB (0 disables the hold). FORGE_MIN_FREE_GB overrides that threshold for a process; the e2e harness sets it to 0 so small temporary filesystems can run fixture tasks. Below the threshold, claims pause, doctor reports FAIL, and one operator event per hold names the free space and the bytes forge gc --caches can reclaim. That command deletes target/, node_modules/.cache, and .godot/ from non-running task and job worktrees; --dry-run reports without deleting. Finished tasks also discard these caches immediately, retaining their source worktrees. Doctor reports total worktree disk usage and the ten largest.

A repository may opt into [sandbox] shared_target = true in its trusted forge.toml (off by default). Agents and checks then receive CARGO_TARGET_DIR=FORGE_HOME/cache/<repository-key>/target, where the key is the existing stable hash of the repository path. This kernel-selected directory is bound read-write in the sandbox and survives task cleanup. Cargo's own target-directory lock serializes concurrent builds.

The same file's [sandbox] section lists what the sandbox exposes beyond the attempt's own holes. ro_paths (default ~/.local/share/mise) are toolchains bound read-only, since $HOME is otherwise empty in there and a node or cargo installed under it would be invisible. rw_paths (default ~/.npm, ~/.cargo/registry, ~/.cargo/git) are package caches bound read-write and shared across attempts; lockfile integrity is what makes that safe. Paths that do not exist are skipped.

dependency_cache (optional, no default) is a directory you warm with a repository's dependencies, bound read-only into every attempt at the same path. A task at a trust level whose egress is "model" (public by default) runs with the proxy allowing the model endpoints only, so its checks cannot reach a registry and install from this cache instead (point the toolchain at it, e.g. CARGO_HOME or npm_config_cache, in the repository's setup check). Without it, setup runs with the declared registries as before and forge doctor warns that public work would need the cache. A level with auto_land = false never lands itself: its run ends unverified once the checks pass, with the branch pushed, and a person lands it with forge land <id> or from the inbox page.

A check named setup runs before the others and gates them: if it fails, nothing else runs. Its outputs (node_modules, target) must be gitignored, or the next attempt fails L0 for a dirty tree.

Workflows are lists of actions: directives (an agent step) and operations (a command, zero dollars). An operation gets the task's facts as FORGE_* environment variables, may commit what it changes (the kernel verifies the result), and may produce the interface a coder is shown. See docs/ACTIONS.md.

Layout

src/agent.rs        spawn the CLI, parse stream-json, timeout
src/agent/inputs.rs documented invocation and streaming-phase argument structs
src/binary.rs       stable forge launch paths across binary replacement
src/argument_policy_tests.rs  require reasons above argument-count allowances
src/assess.rs       the assess directive: a read-only score of a landed diff's maintainability
src/attempt.rs      one attempt of a directive: prompt, launch, verdict, the row
src/audience.rs     task notification audience after recovery and daily digest events
src/audit.rs        diagnosis for a terminal failure; cost anti-patterns
src/builtins/       built-in actions, operations, and workflows, as TOML
src/chat/           Ask Forge: forge chat's fixed tools, the confirm gate in front of the writes, the redactor, and the turn loop (docs/CHAT.md)
src/checks.rs       run one command as a check under a timeout
src/cli.rs          commands and all terminal output
src/concierge.rs    forge ask: sorts a customer message into request, question, need, or unclear
src/config.rs       forge.toml and config.toml
src/ctx.rs          Forge: paths, store, budget, sandbox, reporter, built once
src/deploy.rs       forge deploy: run a target's method, record the result, roll back on failure
src/deploy_look.rs  the deploy-look directive: a read-only agent looks at the deployed page
src/directive.rs    one launcher for every bounded agent run, one reading of how it failed
src/doctor.rs       forge doctor (doctor/watch.rs: the reliability watches, docs/OPS.md "What doctor watches")
src/egress.rs       the egress allowlist and its proxy
src/engine.rs       run_task / run_attempt, Fault::{Task, Env}
src/environment.rs  environment needs: the recognizer, the [environment] grant table and the supervisor's ceiling
src/env_supervisor.rs  an uncovered host or cache need: the supervisor approves within the ceiling or denies to the operator
src/envelope.rs     the result contract: schema and parser
src/experiment.rs   the economist: experiment.toml's weighted draw and its weekly rebalance
src/git.rs          the few git operations Forge performs
src/graph.rs        forge graph: the module graph as data, from forge-repomap edges
src/guard.rs        the landing guard: a pre-receive hook rejecting hand pushes to the base branch, and the emergency override
src/handoff.rs      the fresh continuation arm: a handoff built from git, the journal and the log
src/host.rs         the host at a moment: load per core and swap in use, sampled when an agent process dies
src/init.rs         forge init: data directory, config template, committed workflow catalog, web.token, systemd units
src/unit_path.rs    the PATH the systemd units carry: composed at init, read back from the unit by doctor and the worker
src/intake.rs       intake acceptance: a confirmed brief becomes a project
src/job.rs          forge job start: the executor for operation-only run workflows
src/journal.rs      what earlier attempts in a piece of work said, and what the kernel found
src/landing.rs      the integrator: merge base in, re-verify, push, fast-forward
src/adopt.rs        forge adopt: a hand-made branch verified as it is and landed through the integrator, no agent run
src/admission.rs    Jev at the claim: admission for contact and public tasks, the judgment band recorded on the task, and the size a local model predicts, which routes the workflow (size.rs)
src/risky.rs        risky paths hold for a verdict: a change to the repository's risky paths or lines lands on Jev's reading of the review, else waits for the operator
src/lineage.rs      forge audit's pure lineage walk: tip-of, classify, report
src/main.rs         entry, unix_now
src/operation.rs    a workflow step that is a command, not an agent
src/plugins.rs      plugins: directories named for their plugin.toml, one broken manifest never stops the rest
src/pricing.rs      pricing: a claude attempt priced at list from the CLI's tokens, its own figure kept beside
src/profile.rs      a workflow's measured cost and success, from its runs
src/prompts.rs      what each contract's agent is told, assembled from pieces
src/queue.rs        how a task comes to exist; enqueue validates a TaskRequest
src/redraw.rs       a held experiment arm is re-drawn among the free ones at claim time
src/reload.rs       config reloads between claims: re-read, re-validate, keep the old config on a bad edit
src/render.rs       text rendering for documents: first sentence, path-like tokens stripped, word-boundary cuts
src/report.rs       typed events; the stderr printer is one consumer
src/executor.rs     executor contract, backend selection, and guarantees
src/disk.rs         free-space claim holds, worktree sizes, and build-cache sweeping
src/housekeeping.rs worktree retention: the worker's hourly pass and `forge gc`
src/sandbox.rs      bubblewrap
src/secret_store.rs machine-local credentials: persistent backend selection, OS keychain or encrypted file storage, and secret references
src/job/alerts.rs       what a finished job tells the operator unasked: a failed doctor-daily, new doctor rows, a step that printed nothing
src/plugins/selftest.rs forge plugin test: a plugin's own end-to-end proof, run by forge doctor while it is enabled
src/job/secrets.rs      [secrets] names, a run step's declared secrets and egress hosts, and the redaction of their values
src/load.rs        the time scale a loaded host stretches check timeouts and test deadlines by
src/login.rs        the agent login: a refreshed token written back over the host file, an empty one never seeded
src/login_hold.rs   a provider whose agent login was refused, held until a probe answers; the worker's ten-minute probe and doctor's
src/successor.rs     the successor worker: a staged release starts forge work on it, the old worker drains
src/store/          SQLite, forward-only migrations by user_version, one file per table family (task_write.rs: the one write of a task row; workers.rs: the registered workers and their releases; owners.rs: a running row's owner, pid plus start time, and orphan detection; claim_marks.rs: what the claim writes before the first attempt, the band, the predicted size and a rerouted workflow)
  mod.rs            types, column lists, open, schema_version, the migration runner
  migrations.rs     MIGRATIONS: every forward-only schema migration, in order
  notifications.rs  durable daily counts of follow-ups, retries, answered questions, and superseded blocks
  alerts.rs         standing operator alerts (a failed doctor-daily, a doctor WARN/FAIL row) and, through the message record, whether each was sent
  retry.rs          statement retries for SQLite busy and locked errors, with backoff up to one minute
  run_cursor.rs     the task's stored run cursor (tasks.run_json): the step a requeued or orphaned run resumes at
  tasks.rs          tasks: claim, queue, dependents, lineage
  priority.rs       task priority (0-7, default 2): the claim-order query and parse_priority's CLI aliases
  adoption.rs       a task's origin (agent or adopted), the adopted branch and commit, and the manual count in forge stats
  arms.rs           insert_task_armed: insert a task and draw its journal/explore arms in one transaction
  attempts.rs       attempts and ops: insert, finish, rate limits, tool facts
  jobs.rs           jobs, job_steps, job_effects
  job_resolutions.rs how a job's needs_human closed, by the answer to its job question
  job_runs.rs       recovered job run numbers and cumulative step costs
  deploys.rs        deploys, deploy_targets, assessments
  descendants.rs    the live tasks below and beside a task in its retry lineage, and the `withdraw --abort` decision a worker reads
  doctor/flaky.rs   doctor's flaky-tests row: test-check failures in the last 24h whose failing tests pass again on the base
  lineage.rs        task_ids_since/roots_since: which lineage roots had activity in a window, for forge audit and doctor's dangling row
  supersede.rs      tasks.supersedes: the newest task that superseded one, and the check that reopens a blocked task once its re-pointed `--after` is past blocking
  projects.rs       projects, project_repos, backlog, initiatives, portal_tokens
  record.rs         decisions, task_refs, plugins
  chat.rs           chat_sessions and chat_turns: every turn of Ask Forge, its tool calls and cost
  messages.rs       messages: one row per inbound/outbound message on a channel, so a rule can ask "has this contact replied since"
  holds.rs          provider_holds and provider_probes: a provider held for a refused login, and every probe of it with its cost
  initiative_holds.rs  initiative_holds: which initiative holds have been announced and with what reason, so a successor worker never repeats one a predecessor already made
  webhooks.rs       webhook_tokens: per-hook tokens (only their hashes) that let `forge job fire` start a webhook-triggered job
  events.rs         event_cursors: per project and run workflow, the events.jsonl offset its event trigger has examined up to
  watch.rs          doctor_marks and doctor_samples, and the step runs, verdicts, worker starts, stale jobs and refusals forge doctor's reliability watches read
  schedule.rs       schedule_refusals: the schedules a per_day cap holds back, which the worker's tick records and `forge job list` prints
  stats.rs          forge stats: workflow/step/role/human-attention/time-to-live queries
  questions.rs      forge stats --questions: every task that blocked with a question, and how it was settled
  factors.rs        forge stats --factors: factor levels, the main-effects fit, size classes
  stats_tests.rs    stats.rs's #[cfg(test)] mod, split out to keep stats.rs under the line bound
  daily.rs          StatsDoc.daily: landings and spend per UTC day, the /stats chart's kernel query
src/supervisor.rs   the rung between a blocked task and the human; mechanic.rs: the same rung for a task that ends failed
src/tools.rs        what an attempt ran, read back from its stream
src/release.rs      releases as directories under FORGE_HOME/bin, the atomic current/previous pointers
src/upgrade.rs      forge upgrade: verify, backup, unpack a release, flip current, migrate, restart
src/verify.rs       L0/L1/L2, the claim rule, and the pure verdict table
src/view.rs         shapes behind `log`, `requests`, `decisions`: text and JSON from one struct
src/worker.rs       drive, the queue loop, signals; worker/schedule.rs: the schedule tick and its refusal log; worker/tests.rs: worker unit tests
src/workflows.rs    the workflow and action tables, loaded as one Catalog
tests/e2e/          the real binary against fake agents in tests/fakes/

client/   forge-client: the one Rust client of the CLI; typed rows from --json
portal/   forge-portal: read-only project page at /p/<token>, no login
repomap/  forge-repomap: symbol index and task-ranked file list
forge-test/  forge-test: the failing-test parser (go, pytest, jest, cargo) and its binary
tui/      forge-tui: the operator's seat, a client of the CLI only
web/      forge-web: the same seat in a browser

Environment

  • FORGE_HOME (default $XDG_DATA_HOME/forge or ~/.local/share/forge) holds forge.db, config.toml, worktrees/, logs/. Separate from Forge 1's FORGE_HOME. For one release, an old install with no FORGE_HOME set and no new default directory yet falls back to its pre-rename data directory and old-named environment variables; forge doctor says exactly what to move and rename.
  • FORGE_CLAUDE_BIN overrides the agent binary. Anything that accepts the same flags and emits stream-json works; the tests use shell scripts.
  • FORGE_SANDBOX=0 runs the agent and checks directly on the host. Without it, a machine with no bwrap runs a repository that declares no [execution] backend on the host (doctor warns); one that declares backend = "bwrap" refuses to run.

Building

cargo build --release
cargo test

The e2e suite runs its fakes under the real sandbox, so it requires bwrap; a missing bwrap fails the suite loudly rather than silently skipping sandbox coverage. Set FORGE_TEST_NO_SANDBOX=1 to run the suite unsandboxed on a machine without bubblewrap. The egress tests need a bwrap that can create a network namespace and skip themselves where it cannot (bwrap inside another bwrap).

Where this is going

docs/REVIEW.md is the architectural review of the first week and the staged refactor plan that follows from it; docs/LATER.md holds ideas not yet earned.

The supervisor

When a task blocks with a question, a read-only agent on a strong model reads the repository's record and answers with citations, files a prerequisite task, or escalates to you. Every answer is a decision row whose outcome is the task it re-queued, so forge decisions shows which of its answers landed. See docs/SUPERVISOR.md; [supervisor] in the data-dir config sets the model and the cap on answers per piece of work.

Clients

forge-tui (in tui/) is the operator's seat: the queue, the blocked tasks waiting on a decision with what each agent tried, and a task's trace with its diagnosis; r retries the task under the cursor, R retries it with everything that waited on it. It is a client of the CLI and nothing else: it reads log --json, requests --json, and trace --json, acts through forge retry, and never opens the database or links the kernel (tests/boundary.rs enforces that). Run it with forge on PATH or FORGE_BIN set; FORGE_HOME passes through. forge-tui --dump prints one frame without a terminal.

forge-web (in web/) is the same seat in a browser: the queue, the open questions with what each agent tried, a task's trace with its attempts, diagnosis and journal, and the live event feed. It is the same kind of client: reads use forge verbs' JSON (snapshot, log, trace, journal), and the feed is events --follow as server-sent events. It also has write routes: answer and withdraw run forge answer and forge withdraw; retry and land run forge retry and forge land; initiative settings run forge initiative set; gc and deploy run forge gc and forge deploy. Each goes through the CLI verb via forge-client, as do workflow and plugin controls and webhook job firing (see the client contract). forge task set is available in the CLI; this checkout has no web route for it. It binds 127.0.0.1:7788 unless told --bind, and every request needs the token it generates once into FORGE_HOME/web.token: it prints the link at start, the first visit sets a cookie. There are no routes without the token, so a tailnet proxy in front of it exposes nothing by itself.

Clients never poll. Every event the engine emits is appended as one JSON line to events.jsonl under FORGE_HOME; forge snapshot returns the tasks, the requests, the worker, and the log's byte offset at that instant, and forge events --since <offset> --follow is the subscription from there. A client applies events to its own state and re-reads a task only when an event says it changed. See docs/CLIENT.md for the full contract: every verb a client may call, every JSON document's fields, every event type, and which listing to re-read on which event.

The worker runs as a user service: forge init writes it (init::worker_unit is the only source, not a checked-in file), with a stop timeout long enough to drain a running attempt. After a rebuild, systemctl --user restart forge-worker; forge doctor warns when the running worker's binary has been rebuilt underneath it. A deploy target using the deploy-self method does the rebuild and the restarts on every landing on this repository (docs/DEPLOY.md, "Deploying Forge itself").

License

MIT; see LICENSE.

About

A Software Factory

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages