# Project export: Codex-Loop

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Real automation that goes beyond skills and workflows. Setup your loops to define what automations you like, with fine grained details. Levels up devs by taking full responsibility of multi-step tasks
- Devpost: https://devpost.com/software/orchestra-982zi5
- GitHub: https://github.com/mauri3112/codex-loop
- Video: https://www.youtube.com/embed/eLlXfH8NkIQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Cesar Mauricio (13 commits)

## Devpost submission (written by the team)

### Inspiration

Codex is an amazing tool, but I felt it was still missing a good implementation for turning multi-agent work into durable, reusable workflows. Codex already has strong building blocks. It can delegate work to different agents, use different models and reasoning effort levels, run scheduled tasks, and use skills for procedural work. But there are many use cases where these individual solutions are not enough. I wanted a higher-level layer where the entire process—the agents, dependencies, context, verification, limits, and human decisions—can be defined, inspected, reused, and executed again. Claude Code also inspired me with its configurable subagents and multi-agent workflows. But I wanted to approach the orchestration topic differently and build something simple enough to understand visually, while still being general enough for many different use cases. So I thought: why not use this opportunity to present myself to the Codex team with what I believe could be one of the next big steps in agentic coding: Loops. What this project defines as Loops Loops are reusable, multi-step workflows. Each step is represented by a node, and the nodes are connected through edges. A node represents a unit of work or orchestration logic. It can be an Agent with a task to solve and an explicit definition of done, but it can also represent a condition, verification step, human approval gate, map, join, bounded loop, or subworkflow. An Agent node defines which model and reasoning effort to use, which capabilities it needs, which Context Blocks it is allowed to read, and what should happen if it fails. A retry can even upgrade the model before trying again. The output of a node becomes context and evidence that following nodes can use. This makes context propagation explicit instead of simply putting everything into one increasingly large conversation. A supervisor observes the health of the Loop. It can detect failed or stalled work, allow additional retries, upgrade the model used for recovery, or escalate the problem to the user. The supervisor does not take over the Agents’ tasks. Its purpose is to watch the workflow and help it recover when the original execution plan is no longer progressing as expected. An edge defines the logic path between nodes: what must run before another step can begin and which outputs should be passed forward. Edges can also define what should happen when a node fails, whether the next node should remain blocked, whether the workflow may continue with a warning, or whether the user needs to be asked. The complete Loop also has limits for concurrency, total agents, iterations, tokens, wall-clock time, and repeated rounds without progress. This is important because a workflow called a “Loop” should probably also know when to stop looping :) Why Loops To keep it simple, Loops solve a combination of challenges that individual agents, scheduled prompts, or reusable skills do not completely solve on their own: Use different models and reasoning effort levels for different steps, depending on how difficult or important they are. Run independent work in parallel while keeping dependent work in the correct order. Give every Agent only the context it actually needs instead of sharing one giant conversation. Make the definition of done, verification, retries, approvals, and failure behavior explicit. Keep long-running workflows bounded through budgets, stop conditions, and no-progress detection. Pause, resume, inspect, and intervene in work without losing the history of what happened. Reuse the same workflow manually, on a schedule, or through a webhook. What makes Codex Loop different is the combination of a chat-first experience and an inspectable visual graph. The user does not need to understand graph orchestration before getting started. They can simply describe the outcome they want, and the Loop Designer creates the agents, dependencies, verification steps, integrations, and safety limits. The graph then becomes an explanation and an audit trail of what Codex plans to do—not a complicated form the user has to complete before anything can happen. At the same time, users who want more control can open the visual editor and adjust the individual nodes, edges, models, effort levels, retries, context access, and execution rules themselves.

### What it does

Codex Loop lets users describe, create, run, and reuse custom multi-agent workflows. The user starts by describing an outcome in natural language. A persistent 5.6-Sol Loop Designer inspects the available capabilities and creates a schema-constrained workflow proposal. It records reasonable assumptions and asks only when missing information would materially change the safety, architecture, access, cost, or definition of done. The result is a versioned draft that the user can continue refining through chat. Each change creates a new revision, and changes can be undone without silently rewriting the history. The user can also switch to the graphical editor for more precise manual adjustments. Before real execution, the Loop can be simulated. Simulation validates the dependency plan, previews what each node would do, and checks workspace and capability availability without starting Agent threads or modifying files. Once the Loop is ready, it can be published and: Run once with an optional additional prompt and project directory. Run automatically on a configurable schedule. Be triggered through a webhook with input parameters. During execution, Codex Loop creates a native Codex thread for every Agent node. Independent nodes can run in parallel, while dependent nodes wait for their required inputs. Context Blocks control which information each Agent receives, and completed work can create checkpoints that are reused when the workflow definition and repository revision still match. The interface streams the actual Agent messages, commands, tool calls, approvals, file changes, retries, failures, and final outputs. Previous runs keep frozen execution results, so a new run cannot overwrite the evidence from an older one. The user can also pause scheduling, stop active turns, answer Agent questions, approve requested actions, steer an active Agent, queue a follow-up instruction, or share new context with selected future Agents. So it is not just a graph that looks like agents are working. It is a local control plane running real Codex threads and keeping the entire execution inspectable. How I built it I built the frontend with React, TypeScript, Vite, and React Flow. The graph is used both as a visual editor and as a live representation of the execution state. The backend is an Express server with local JSON persistence. For real execution, the backend communicates with codex app-server through its JSONL protocol over stdin and stdout. Each Agent node is mapped to a persistent native Codex thread, and the bridge handles starting and resuming threads, starting turns, streaming events, requesting approvals, answering questions, steering, interruption, and token usage. The Loop Designer is itself a persistent, read-only Codex thread. It receives the current workflow, the user request, and the discovered capabilities, then returns a schema-constrained proposal that can safely be compiled into the next graph revision. I also built an authenticated MCP interface and packaged design-loop and operate-loop skills. This means a user can create or operate a Loop from another Codex task without needing to begin in the visual application. Challenges I ran into My project is intended as inspiration for the Codex team: a cool feature that I think, if polished well, could become an amazing part of the app. But since I do not have access to the Codex source code itself, I had to work around that limitation. That is why I built on top of the Codex CLI and app-server, and created a limited replica of the Codex interface around it. Thankfully, that was enough to build a functional prototype, but it also means I had to build a bridge instead of implementing the feature directly inside Codex. Nevertheless, it was 5.6-Sol’s work to make it happen, so this is mainly a limitation I think you should take into account while assessing the project. Just close your eyes slightly and imagine it is actually Codex, and not a frontend running a copy of it :) The other big challenge was that orchestration becomes much more complicated once the graph needs to do real work. Starting multiple agents is the easy part. The difficult part is deciding which context they receive, what completion means, how retries behave, when branches may continue independently, what pause and stop actually mean, how human questions are routed, how stale approvals are rejected, and how to prevent a repeating workflow from wasting unlimited time and tokens. The final challenge was exposing all that power without making the user design a distributed system before fixing a bug. This is what led me to make chat the primary experience and the graph the secondary, more precise interface. Accomplishments that I’m proud of I love the concept of using an actual sun, moon, and Earth for the graph nodes. I think this was a clever idea and adds a polished, recognizable look to the graph. The different effort levels also have their own visual hints, so the user can understand the expected intensity of a node just by looking at it. Although I think that already looks cool, with a little more time I could probably have made it as strong as the node-type visuals. You get the idea. I am actually more of a backend developer, but I also think I have a lot of potential as a UX designer because I think I understand good the how to make the user experience as best as possible. That is why I decided in the middle of the project to make the graphical interface the secondary interaction and the chat interface the first-class experience. A skill that helps users define what they want became a natural choice: users can describe their outcome first, and only go into the graphical interface when they want to inspect or manually refine the details. I am also proud that the result is more than a visual mockup. The prototype runs native Codex threads, keeps durable execution history, scopes context, handles retries and attention, enforces budgets, and supports real reusable triggers. There is still plenty left to polish, but the important architectural idea is actually there and working. What I learned I learned a lot about agent-harness work during this project. At first, I thought my Loop concept was easy, which was also why I thought it was such a unique and great idea. But halfway through the work and research, I started to understand why orchestration is genuinely challenging. Building one shoe that fits every foot or one workflow abstraction that fits every use case was tough. The hardest part is not asking several models to do several tasks. The hard part is designing the contracts between them: dependencies, context boundaries, definitions of done, verification, failure handling, budgets, and human intervention. I also learned that the best orchestration interface may not look like orchestration at first. Most users should not need to think in nodes and edges. They should be able to explain what they want in chat, while the graph quietly gives them transparency, control, and confidence. The graph then can contain a lot of complexity but abstract the execution in a simple way that the user can easily extend and improve.

### What's next

for Codex Loop I would add automatic per-node Git worktrees, richer Observer policies, external notifications when a Loop needs attention, stronger authentication and permissions, better secret-provider bindings, and more production-ready execution storage. The visual language could be developed further as well. I would love to make model strength, effort, cost, context flow, and risk readable almost entirely from the graph. And now, I hope you find this funny. I had a lot of fun building the project, so basically, here comes my résumé for you xD. I’ll keep it super short: I started working with chatbots and NLP at university many years ago, doing classical intent ranking and scoring with a Solr database and similar technologies. Since then, I have always been involved with the application side of language models. I worked at Telekom as an AI Engineer, building a coding agent before today’s coding harnesses existed, as well as an AI Operator intended to work as a first-level support system. Now I am an AI Engineer at Würth IT, building an orchestration system with voice as the first-class experience. So I have seen the application side of language models from before the current boom. I was actually an early fan of GPT-2 because I was born in Bolivia, and one of its example texts examples was about a scientist in Bolivia and the Andes region. The YouTube channel Computerphile made an explanation video about it, and I was amazed. If you are interested in my idea, or simply want to chat about why I think you should build a V2 of this project, feel free to contact me. I have a full-time job, so this project was built during a few hours of work each day. But I am open to starting a new challenge if you would like me to join you. I hope you had as much fun reading this as I had building it, and hope you find inspiration in it for codex.

## README (from the GitHub repository)

# Codex Loop

Codex Loop is a local, chat-first control plane for designing, running, and supervising reusable Codex workflows. Describe an outcome to the persistent Loop Designer; it turns the request into a validated, versioned execution graph and keeps the full graphical editor behind an explicit **Edit visually** action.

![Codex Loop designing and running a workflow](docs/codex-loop-demo.gif)

## What it provides

- Chat-first graph creation and revision through a read-only GPT-5.6 Sol Designer thread.
- A read-only graph preview plus a graphical editor for nodes, dependencies, budgets, retries, capabilities, context access, and supervisor behavior.
- Agent, map, join, condition, loop, verify, gate, and subworkflow orchestration nodes.
- Per-node Sol, Terra, or Luna selection, reasoning effort, retry upgrades, and a post-design assessment of model fit, expected token-use drivers, and current Codex credit costs.
- Live discovery of available Codex skills, apps, MCP servers, Computer Use, and supported CLI capabilities.
- Native Codex threads backed by `codex app-server`, including streamed messages, tool calls, file changes, approvals, user-input requests, token usage, and status updates.
- One-time, scheduled, and webhook-triggered runs; immutable run history; simulation; interventions; checkpoints; bounded retries; and no-progress detection.
- Durable JSON storage and Docker packaging for a local workstation or trusted home server.
- An authenticated Streamable HTTP MCP surface plus packaged `design-loop` and `operate-loop` skills for controlling Loop from another Codex task.

## How it works

The browser is an operator interface, not a Codex credential holder. The Express server owns the authenticated `codex app-server` subprocess and exposes narrow workflow and thread operations to the UI. Each graph node maps to a persisted native Codex thread. Loop owns dependency scheduling, budgets, checkpoints, capability bindings, context routing, gates, and supervision; Codex owns the reasoning and tool-use loop inside each worker.

The Designer returns schema-constrained proposals. Loop compiles each complete proposal into a new workflow revision, validates it, records the mutation, and shows assumptions or up to three consequential questions. Creating or revising a graph also produces a model-selection assessment that explains:

- whether each model and reasoning effort matches the task complexity;
- how context, reasoning effort, graph fan-out, tools, retries, and the token ceiling affect usage;
- the comparative Codex credit rates for Sol, Terra, and Luna; and
- when a cheaper or stronger model would be a better tradeoff.

Credit rates can change, so the Designer points users to the [current Codex pricing page](https://learn.chatgpt.com/docs/pricing). Actual run cost depends on the input, cached input, output, reasoning, tools, and retries consumed by that run.

## Quick start from source

Prerequisites:

- Node.js 22 and npm.
- A current Codex CLI available as `codex`.
- An authenticated local Codex session (`codex login status`).
- A repository or projects directory that workers may use as their workspace.

```bash
npm ci
codex login status
npm run dev
```

Open `http://127.0.0.1:5173`. Vite proxies API requests to `http://127.0.0.1:4317` during development.

For a production-style source run:

```bash
npm run build
npm test
npm start
```

Open `http://127.0.0.1:4317` after the build. The server serves the compiled interface and API from the same origin.

For the simplest agent-oriented local container build, follow [build.md](build.md). That guide builds and runs one local Docker image and intentionally excludes release tagging, GHCR, Watchtower, Caddy, and home-server setup.

## Designing a Loop

1. Open **Loop** and create a blank Loop or start from a template.
2. Describe the outcome in the Designer chat.
3. Review the graph, the Designer response, model/cost assessment, assumptions, setup requirements, and validation issues.
4. Refine the graph in chat, or choose **Edit visually** to change it directly.
5. Simulate the Loop for a non-persisted, read-only readiness report.
6. Save and publish a valid revision, configure its trigger, and run it.

Visual edits support node creation and deletion, connection management, drag positioning, orchestration settings, model and effort selection, retry policy, task capability autocomplete, context visibility, secrets-by-reference, run budgets, and supervisor escalation policy. Secret values do not belong in a Loop definition.

## Node kinds

| Kind | Purpose |
| --- | --- |
| `agent` | Run one bounded Codex worker task. |
| `map` | Fan out independent work over a collection within concurrency and total-agent budgets. |
| `join` | Wait for upstream branches and synthesize their results. |
| `condition` | Select supported outgoing routes from evidence. |
| `loop` | Repeat until a declared stop condition or iteration limit is reached. |
| `verify` | Independently test or challenge upstream claims against a rubric. |
| `gate` | Require explicit human approval before continuing. |
| `subworkflow` | Invoke another saved Loop as a bounded child workflow. |

## Run modes

- **Simulate** validates the dependency plan and produces a generic preview. It performs only read-only workspace and capability/authentication probes; it does not create threads, run tools, modify files, or persist a run.
- **Run once** starts a saved Loop with an optional one-time prompt and working directory. It does not overwrite the configured scheduled or webhook trigger.
- **Scheduled run** starts on selected weekdays and times in an IANA time zone. The coordinator prevents duplicate starts for the same scheduled minute.
- **Webhook run** exposes a tokenized `GET` or `POST` endpoint at `/api/triggers/:token`. Query values or a JSON object are merged with configured defaults and made available to the run.

Each run freezes its thread messages, tool calls, file changes, final outputs, checkpoints, token use, and audit events. The operator can pause, resume, stop, answer native user-input requests, resolve gates, steer active work, queue follow-ups, or inject curated context. Token ceilings and maximum no-progress rounds stop unbounded work.

## Models and token use

Loop uses the short names **Sol**, **Terra**, and **Luna** in workflow definitions:

- **Sol** is the highest-capability and highest-cost option for ambiguous, difficult, or high-value work.
- **Terra** balances reasoning quality and cost for everyday implementation and tool use.
- **Luna** is the lowest-cost option for clear, repeatable, high-volume tasks.

Reasoning effort is configured separately. Use the lowest effort that reliably satisfies the node’s definition of done; higher effort, larger context, verbose tool results, fan-out, retries, and long outputs can all increase token consumption. Loop tracks native `thread/tokenUsage/updated` events against an optional workflow token ceiling.

## Persistence and security

By default, workflow definitions, native thread mappings, mutations, run records, and execution evidence live in `data/codex-loop.json`. Keep `data/` persistent when replacing a container. The Docker setup also mounts the host Codex home and workspace instead of baking credentials or user data into the image.

Codex Loop can launch native Codex workers that read and modify the mounted workspace according to `CODEX_LOOP_SANDBOX`. It has no built-in public-user authentication for the web interface. Keep it on a trusted local machine, LAN, or private network. Do not expose it to the public internet without adding authentication and TLS, and never commit `.env`, Codex credentials, tokens, secrets, or workflow state.

## Configuration

| Variable | Default | Purpose |
| --- | --- | --- |
| `HOST` | `127.0.0.1` | API/UI bind address. The container sets `0.0.0.0`. |
| `PORT` | `4317` | API/UI port. |
| `CODEX_BINARY` | `codex` | Codex CLI executable path. |
| `COD

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 95 recognized source files, 686 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (107 of 107)

```
.dockerignore
.env.example
.github/workflows/release.yml
.gitignore
AGENTS.md
build.md
codex_loop.md
docker-compose.yml
Dockerfile
docs/attention-and-intervention.md
docs/claude-code-workflow-parity.md
index.html
LICENSE
package.json
plugins/codex-loop/.codex-plugin/plugin.json
plugins/codex-loop/.mcp.json
plugins/codex-loop/skills/design-loop/agents/openai.yaml
plugins/codex-loop/skills/design-loop/references/requirements.md
plugins/codex-loop/skills/design-loop/references/security.md
plugins/codex-loop/skills/design-loop/references/workflow-ir.md
plugins/codex-loop/skills/design-loop/SKILL.md
plugins/codex-loop/skills/operate-loop/agents/openai.yaml
plugins/codex-loop/skills/operate-loop/references/runtime-safety.md
plugins/codex-loop/skills/operate-loop/SKILL.md
README.md
scripts/check-latest-release.sh
scripts/verify-bridge.ts
server/app.test.ts
server/app.ts
server/attention-supervisor.test.ts
server/attention-supervisor.ts
server/codex-app-server.ts
server/codex-bridge.test.ts
server/codex-bridge.ts
server/fixtures/fake-app-server.mjs
server/index.ts
server/loop-designer.test.ts
server/loop-designer.ts
server/mcp.ts
server/run-coordinator.test.ts
server/run-coordinator.ts
server/simulation.test.ts
server/simulation.ts
server/store.ts
server/trigger.test.ts
src/api/client.ts
src/App.tsx
src/components/activity/activity.css
src/components/activity/ActivityPanel.tsx
src/components/canvas/canvas.css
src/components/canvas/CanvasNodes.tsx
src/components/canvas/celestial.test.ts
src/components/canvas/celestial.ts
src/components/canvas/index.ts
src/components/canvas/LoopWorkspaceCanvas.tsx
src/components/canvas/types.ts
src/components/canvas/WorkflowEdge.tsx
src/components/designer/designer.css
src/components/designer/LoopDesignerPanel.tsx
src/components/inspector/index.ts
src/components/inspector/inspector.css
src/components/inspector/SelectionInspector.tsx
src/components/intervention/intervention.css
src/components/intervention/InterventionCenter.tsx
src/components/landing/loop-landing.css
src/components/landing/LoopLanding.tsx
src/components/run/run-control.css
src/components/run/run-history.css
src/components/run/RunControl.tsx
src/components/run/RunHistoryView.tsx
src/components/run/simulation-control.css
src/components/run/SimulationControl.tsx
src/components/shell/codex-shell.css
src/components/shell/CodexShell.tsx
src/components/thread/AgentThreadView.tsx
src/components/thread/thread.css
src/components/ui/Button.tsx
src/components/ui/composer-keyboard.test.ts
src/components/ui/composer-keyboard.ts
src/components/ui/slash-autocomplete.test.ts
src/components/ui/slash-autocomplete.ts
src/components/ui/SlashAutocompleteTextArea.tsx
src/components/ui/StatusIndicator.tsx
src/components/ui/ui.css
src/data/seed.ts
src/domain/context.test.ts
src/domain/context.ts
src/domain/definition.test.ts
src/domain/definition.ts
src/domain/models.ts
src/domain/normalize.test.ts
src/domain/normalize.ts
src/domain/simulation-report.ts
src/domain/simulation.test.ts
src/domain/simulation.ts
src/domain/task-capabilities.ts
src/domain/types.ts
src/domain/workflow.test.ts
src/domain/workflow.ts
src/main.tsx
src/styles/app.css
src/styles/global.css
src/styles/tokens.css
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts
```

### Dependencies

- package.json: @playwright/test@^1.55.0, @types/express@^5.0.3, @types/node@^22.15.30, @types/react@^19.1.10, @types/react-dom@^19.1.7, @vitejs/plugin-react@^4.6.0, @xyflow/react@^12.10.0, concurrently@^9.2.1, express@^5.1.0, lucide-react@^0.468.0, react@^19.1.1, react-dom@^19.1.1, react-router-dom@^7.8.2, tsx@^4.20.5, typescript@^5.8.3, vite@^7.1.3, vitest@^3.2.4, zod@^4.1.5, zustand@^5.0.8

### Recent commits (newest first)

- Fix primary button contrast
- Finish Loop graph guidance and build docs
- Add read-only Loop simulation and keyboard regression coverage
- Cover all Designer model assignments
- Improve Loop runs, history, and editor UX
- Make workflow persistence concurrency-safe
- Finish Loop execution controls and navigation
- Add chat-first Loop designer and orchestration
- Automate releases and home-server deployment
- Add safe agent node deletion
- Add task capability slash autocomplete
- Add attention and intervention supervision
- Initial open-source release

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
# AGENTS.md

## Release and deployment

- Every push to `main` is a release event. Keep `.github/workflows/release.yml`,
  `Dockerfile`, `docker-compose.yml`, and the deployment section in `README.md`
  aligned.
- A successful release publishes immutable `v1.0.<run-number>` and moving
  `latest` images to GHCR, then creates the matching GitHub release.
- The local Compose deployment joins the external `home-server-proxy` network;
  Caddy and LAN DNS are owned by the sibling `home-server-setup` repository.
- Runtime state under `data/` and the mounted Codex home/workspace must survive
  image replacement. Never bake credentials or workflow data into an image.
- Validate changes with `npm test`, `npm run build`, `docker build`, and
  `docker compose config`. For deployment changes, also verify `/api/health`,
  `/api/version`, and `scripts/check-latest-release.sh` against the live route.

## Home-server cross-reference

- Application container ownership: this repository.
- Caddy route, landing page, DNS guidance, and operator documentation:
  `/Users/mauri-home/Documents/projects/home-server-setup`.
- Update the sibling repository's `README.md` and `SETUP.md` whenever the route,
  container name, network, port, or deployment procedure changes.

## Safety

- Codex Loop can launch native Codex agents against the mounted workspace.
  Expose it only on a trusted LAN or private network; do not forward it from the
  public internet without adding authentication and TLS.
- Do not commit `.env`, Codex credentials, workflow state, tokens, or secrets.


```

### build.md

```markdown
# Build Codex Loop locally with Docker

This is the minimal build path for an agent or developer who needs a local production-style Codex Loop container. It does not use the repository’s release tags, GHCR image, Watchtower updater, Caddy route, external `home-server-proxy` network, or home-server DNS.

## Prerequisites

- Docker Engine or Docker Desktop is running.
- The current directory is the Codex Loop repository.
- The host has an authenticated Codex home at `${HOME}/.codex`. Confirm it with `codex login status` before starting the container.

## Build the image

```bash
docker build -t codex-loop:local .
```

The Dockerfile builds the React application, installs the pinned Codex CLI in the runtime image, and starts the Express server on port `4317`.

## Run one local container

Create the persistent data directory, then run the image:

```bash
mkdir -p data

docker run --detach \
  --name codex-loop-local \
  --publish 4317:4317 \
  --env HOST=0.0.0.0 \
  --env PORT=4317 \
  --env CODEX_HOME=/root/.codex \
  --env CODEX_LOOP_WORKSPACE=/workspace/codex_loop \
  --env CODEX_LOOP_SANDBOX=workspace-write \
  --env CODEX_LOOP_DESIGNER_MODEL=gpt-5.6-sol \
  --mount type=bind,src="$(pwd)/data",dst=/app/data \
  --mount type=bind,src="${HOME}/.codex",dst=/root/.codex \
  --mount type=bind,src="$(pwd)",dst=/workspace/codex_loop \
  codex-loop:local
```

The mounts keep workflow state on the host, reuse the host’s Codex authentication, and make this repository available to workers. To let Loops work across several repositories, replace the final mount source with the absolute path to the desired projects directory and set `CODEX_LOOP_WORKSPACE` to the corresponding container path.

Do not add secrets to the Dockerfile or image. To protect the MCP endpoint, add `--env CODEX_LOOP_MCP_TOKEN=replace-with-a-long-random-value` when starting the container.

## Verify the container

```bash
curl --fail http://127.0.0.1:4317/api/health
curl --fail http://127.0.0.1:4317/api/version
docker logs codex-loop-local
```

Open `http://127.0.0.1:4317`. A local image reports `development`, `unknown`, and `unknown` version metadata unless build arguments are supplied; that is expected for this simple build.

## Rebuild after source changes

```bash
docker stop codex-loop-local
docker rm codex-loop-local
docker build -t codex-loop:local .
```

Then repeat the `docker run` command above. The bind-mounted `data/` directory is not removed when the container is replaced.

## Stop and remove the container

```bash
docker stop codex-loop-local
docker rm codex-loop-local
```

Keep `data/` if existing Loop definitions and run history should survive the next container.

```

### Dockerfile

```
FROM node:22-bookworm-slim AS build

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-bookworm-slim AS runtime

ARG CODEX_CLI_VERSION=0.144.5
ARG APP_VERSION=development
ARG APP_REVISION=unknown
ARG APP_BUILT_AT=unknown

ENV NODE_ENV=production \
    HOST=0.0.0.0 \
    PORT=4317 \
    CODEX_LOOP_VERSION=${APP_VERSION} \
    CODEX_LOOP_REVISION=${APP_REVISION} \
    CODEX_LOOP_BUILT_AT=${APP_BUILT_AT}

WORKDIR /app
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates git \
    && rm -rf /var/lib/apt/lists/* \
    && npm install --global "@openai/codex@${CODEX_CLI_VERSION}" tsx

COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
COPY server ./server
COPY src/domain ./src/domain
COPY src/data ./src/data

RUN mkdir -p /app/data /workspace /root/.codex

EXPOSE 4317
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
  CMD node -e "fetch('http://127.0.0.1:4317/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"

CMD ["tsx", "server/index.ts"]


```

### package.json

```
{
  "name": "codex-loop",
  "version": "1.0.0",
  "private": true,
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/mauri3112/codex-loop.git"
  },
  "type": "module",
  "scripts": {
    "dev": "concurrently -k \"npm:dev:server\" \"npm:dev:client\"",
    "dev:client": "vite --host 127.0.0.1",
    "dev:server": "tsx watch server/index.ts",
    "build": "tsc -b && vite build",
    "start": "tsx server/index.ts",
    "test": "vitest run",
    "test:e2e": "playwright test",
    "verify:bridge": "tsx scripts/verify-bridge.ts"
  },
  "dependencies": {
    "@xyflow/react": "^12.10.0",
    "express": "^5.1.0",
    "lucide-react": "^0.468.0",
    "react": "^19.1.1",
    "react-dom": "^19.1.1",
    "react-router-dom": "^7.8.2",
    "zod": "^4.1.5",
    "zustand": "^5.0.8"
  },
  "devDependencies": {
    "@playwright/test": "^1.55.0",
    "@types/express": "^5.0.3",
    "@types/node": "^22.15.30",
    "@types/react": "^19.1.10",
    "@types/react-dom": "^19.1.7",
    "@vitejs/plugin-react": "^4.6.0",
    "concurrently": "^9.2.1",
    "tsx": "^4.20.5",
    "typescript": "^5.8.3",
    "vite": "^7.1.3",
    "vitest": "^3.2.4"
  }
}

```

### docker-compose.yml

```yaml
name: codex-loop

services:
  codex-loop:
    image: ghcr.io/mauri3112/codex-loop:latest
    container_name: codex-loop
    restart: unless-stopped
    environment:
      HOST: 0.0.0.0
      PORT: 4317
      CODEX_HOME: /root/.codex
      CODEX_LOOP_WORKSPACE: /workspace
      CODEX_LOOP_SANDBOX: ${CODEX_LOOP_SANDBOX:-workspace-write}
      CODEX_LOOP_DESIGNER_MODEL: ${CODEX_LOOP_DESIGNER_MODEL:-gpt-5.6-sol}
      CODEX_LOOP_PUBLIC_URL: ${CODEX_LOOP_PUBLIC_URL:-http://codex-loop.home}
      CODEX_LOOP_MCP_TOKEN: ${CODEX_LOOP_MCP_TOKEN:-}
      TZ: ${TZ:-Europe/Berlin}
    volumes:
      - ./data:/app/data
      - ${CODEX_HOME_PATH:-/Users/mauri-home/.codex}:/root/.codex
      - ${CODEX_LOOP_WORKSPACE_PATH:-/Users/mauri-home/Documents/projects}:/workspace
    labels:
      com.centurylinklabs.watchtower.enable: "true"
    networks:
      - proxy

  updater:
    image: containrrr/watchtower:1.7.1
    container_name: codex-loop-updater
    restart: unless-stopped
    environment:
      DOCKER_API_VERSION: "1.44"
      WATCHTOWER_CLEANUP: "true"
      WATCHTOWER_LABEL_ENABLE: "true"
      WATCHTOWER_POLL_INTERVAL: ${UPDATE_INTERVAL_SECONDS:-300}
      WATCHTOWER_ROLLING_RESTART: "true"
      TZ: ${TZ:-Europe/Berlin}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - proxy

networks:
  proxy:
    external: true
    name: home-server-proxy

```

### src/main.tsx

```typescript
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { App } from "./App";
import "@xyflow/react/dist/style.css";
import "./styles/tokens.css";
import "./styles/global.css";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
);

```

### server/index.ts

```typescript
import { createApp } from "./app.js";
import type { Server } from "node:http";
import { CodexBridge } from "./codex-bridge.js";
import { JsonWorkflowStore } from "./store.js";
import { RunCoordinator } from "./run-coordinator.js";
import { AttentionSupervisor } from "./attention-supervisor.js";
import { CodexLoopDesigner } from "./loop-designer.js";

const port = Number(process.env.PORT ?? 4317);
const host = process.env.HOST ?? "127.0.0.1";

const store = new JsonWorkflowStore();
const bridge = new CodexBridge(store);
const designer = new CodexLoopDesigner(store, bridge);
const coordinator = new RunCoordinator(store, bridge);
const attentionSupervisor = new AttentionSupervisor(store);
let server: Server | undefined;

const start = async () => {
  await bridge.prepareRuntime();
  server = createApp(store, bridge, designer).listen(port, host, () => {
    console.log(`Codex Loop API listening at http://${host}:${port}`);
    coordinator.start();
    attentionSupervisor.start();
  });
};

const closeRuntime = async () => {
  await Promise.allSettled([bridge.close(), designer.close()]);
};

const shutdown = () => {
  coordinator.stop();
  attentionSupervisor.stop();
  if (!server) {
    void closeRuntime().finally(() => process.exit(0));
    return;
  }
  server.close(() => {
    void closeRuntime().finally(() => process.exit(0));
  });
};

process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);
void start().catch((error) => {
  console.error("Codex Loop failed to start", error);
  process.exitCode = 1;
});

```

### server/app.ts

```typescript
import { existsSync } from "node:fs";
import path from "node:path";
import express, { type NextFunction, type Request, type Response } from "express";
import { z } from "zod";
import { validateWorkflowDefinition, workflowDefinition } from "../src/domain/definition.js";
import type { Workflow, WorkflowDefinition } from "../src/domain/types.js";
import { createBlankWorkflow, createGeneratedWorkflow } from "../src/data/seed.js";
import { JsonWorkflowStore, WorkflowNotFoundError, WorkflowRevisionConflictError, WorkflowValidationError } from "./store.js";
import { BridgeConflictError, BridgeInputError, BridgeResourceNotFoundError, CodexBridge, type CodexBridgeService } from "./codex-bridge.js";
import { CodexLoopDesigner, type LoopDesignerService } from "./loop-designer.js";
import { handleMcpRequest } from "./mcp.js";
import { simulateWorkflow } from "./simulation.js";

const generateSchema = z.object({ task: z.string().trim().min(1).max(12_000) });
const instructionSchema = z.object({ instruction: z.string().trim().min(1).max(12_000) });
const designerMessageSchema = z.object({ message: z.string().trim().min(1).max(12_000) });
const createThreadSchema = z.object({ task: z.string().trim().min(1).max(12_000) });
const approvalSchema = z.object({ decision: z.enum(["accept", "decline"]) });
const gateDecisionSchema = z.object({ decision: z.enum(["approve", "decline"]) });
const runActionSchema = z.enum(["start", "pause", "resume", "stop", "reset"]);
const singleRunOptionsSchema = z.object({
  additionalPrompt: z.string().trim().max(12_000).optional(),
  workingDirectory: z.string().trim().max(4_096).optional(),
});
const simulationOptionsSchema = z.object({
  workingDirectory: z.string().trim().max(4_096).optional(),
});
const runConfigurationSchema = z.object({
  mode: z.enum(["single", "scheduled", "webhook"]),
  schedule: z.object({
    days: z.array(z.number().int().min(0).max(6)).min(1).max(7),
    times: z.array(z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/)).min(1).max(24),
    timezone: z.string().trim().min(1).max(80),
  }),
  webhook: z.object({
    token: z.string().regex(/^[a-zA-Z0-9_-]{12,128}$/),
    parameters: z.array(z.object({ id: z.string().min(1).max(128), key: z.string().trim().min(1).max(128), defaultValue: z.string().max(2_000) })).max(30),
  }),
});
const triggerValuesSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])).default({});
const interventionSchema = z.object({
  runId: z.string().min(1).max(256),
  idempotencyKey: z.string().min(1).max(256),
  delivery: z.enum(["steer", "queue", "context"]),
  message: z.string().trim().min(1).max(12_000),
  threadId: z.string().min(1).max(256).optional(),
  expectedTurnId: z.string().min(1).max(256).optional(),
  recipientNodeIds: z.array(z.string().min(1).max(256)).max(100).optional(),
}).superRefine((input, context) => {
  if (input.delivery === "context" && !input.recipientNodeIds?.length) {
    context.addIssue({ code: "custom", path: ["recipientNodeIds"], message: "Context interventions require recipients" });
  }
  if (input.delivery !== "context" && (!input.threadId || !input.expectedTurnId)) {
    context.addIssue({ code: "custom", path: ["threadId"], message: `${input.delivery} interventions require a thread and expected turn` });
  }
});
const attentionResponseSchema = z.object({
  runId: z.string().min(1).max(256),
  expectedTurnId: z.string().min(1).max(256).optional(),
  answers: z.record(z.string(), z.union([z.string().max(12_000), z.array(z.string().max(12_000)).min(1).max(20)])),
});
const workflowDefinitionSchema = z.custom<WorkflowDefinition>((value) => Boolean(
  value && typeof value === "object" && typeof (value as WorkflowDefinition).name === "string" &&
  Array.isArray((value as WorkflowDefinition).nodes) && Array.isArray((value as WorkflowDefinition).edges),
), "Invalid workflow definition");
const definitionMutationSchema = z.object({
  baseRevision: z.number().int().nonnegative(),
  actor: z.enum(["user", "designer", "system", "mcp"]),
  rationale: z.string().trim().min(1).max(2_000),
  definition: workflowDefinitionSchema,
});

const workflowSchema = z.custom<Workflow>((value) => {
  if (!value || typeof value !== "object") return false;
  const workflow = value as Partial<Workflow>;
  return Boolean(
    typeof workflow.id === "string" &&
      workflow.id.length > 0 &&
      typeof workflow.name === "string" &&
      typeof workflow.mainTask === "string" &&
      Array.isArray(workflow.nodes) &&
      Array.isArray(workflow.edges) &&
      Array.isArray(workflow.observers) &&
      Array.isArray(workflow.contextBlocks) &&
      Array.isArray(workflow.threads) &&
      Array.isArray(workflow.runs) &&
      Array.isArray(workflow.events),
  );
}, "Invalid workflow payload");

function asyncRoute(
  handler: (request: Request, response: Response, next: NextFunction) => Promise<unknown>,
) {
  return (request: Request, response: Response, next: NextFunction) => {
    void handler(request, response, next).catch(next);
  };
}

export function createApp(
  store = new JsonWorkflowStore(),
  bridge: CodexBridgeService = new CodexBridge(store),
  designer: LoopDesignerService = new CodexLoopDesigner(store, bridge),
) {
  const app = express();
  app.disable("x-powered-by");
  app.use(express.json({ limit: "2mb" }));

  app.get("/api/health", (_request, response) => {
    response.json({ status: "ok" });
  });

  app.get("/api/version", (_request, response) => {
    response.json({
      version: process.env.CODEX_LOOP_VERSION ?? "development",
      revision: process.env.CODEX_LOOP_REVISION ?? "unknown",
      builtAt: process.env.CODEX_LOOP_BUILT_AT ?? "unknown",
    });
  });

  app.post("/mcp", asyncRoute(async (request, response) => {
    await handleMcpRequest(request, response, { store, bridge, designer });
  }));

  app.get("/api/bridge/status", (_request, response) => {
    response.json(bridge.status());
  });

  app.post(
    "/api/bridge/connect",
    asyncRoute(
[truncated — 10236 more characters]
```

### src/App.tsx

```typescript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Navigate, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom";
import { CircleStop, LoaderCircle, MessageSquareMore, MoreHorizontal, Pause, Pencil, Play, RotateCcw, Save, Sparkles, Trash2 } from "lucide-react";
import { api, type CreateInterventionInput, type RespondToAttentionInput } from "./api/client";
import { ActivityPanel } from "./components/activity/ActivityPanel";
import { LoopWorkspaceCanvas } from "./components/canvas";
import { LoopLanding, type LoopTemplateItem } from "./components/landing/LoopLanding";
import { LoopDesignerPanel } from "./components/designer/LoopDesignerPanel";
import { AttentionBanner, InterventionDrawer } from "./components/intervention/InterventionCenter";
import { RunControl } from "./components/run/RunControl";
import { RunHistoryView } from "./components/run/RunHistoryView";
import { SimulationControl } from "./components/run/SimulationControl";
import { CodexShell, type ShellSection } from "./components/shell/CodexShell";
import { AgentThreadView } from "./components/thread/AgentThreadView";
import { Button } from "./components/ui/Button";
import type { AppData, Selection, SingleRunOptions, Workflow, WorkflowRunConfiguration } from "./domain/types";
import type { SimulationOptions, WorkflowSimulationReport } from "./domain/simulation-report";
import "./styles/app.css";

function sectionForPath(pathname: string): ShellSection {
  if (pathname.startsWith("/loop")) return "loop";
  if (pathname.startsWith("/scheduled")) return "scheduled";
  if (pathname.startsWith("/plugins")) return "plugins";
  if (pathname.startsWith("/pull-requests")) return "pull-requests";
  if (pathname.startsWith("/chat")) return "chat";
  return "threads";
}

function StaticCodexScreen({ section }: { section: Exclude<ShellSection, "loop"> }) {
  const copy = {
    threads: ["New task", "Start a task in the current project.", "What should we work on?"],
    scheduled: ["Scheduled", "Review recurring and queued tasks.", "No scheduled tasks"],
    plugins: ["Plugins", "Manage installed Codex plugins.", "Your plugins"],
    "pull-requests": ["Pull requests", "Review active repository changes.", "No pull requests need attention"],
    chat: ["Chat", "Continue a conversation with Codex.", "Start a chat"],
  }[section];
  return <main className="static-screen"><header><h1>{copy[0]}</h1><MoreHorizontal size={16} /></header><section><div className="static-mark"><Sparkles size={20} /></div><h2>{copy[2]}</h2><p>{copy[1]}</p>{section === "threads" && <div className="static-composer">Ask Codex to change, review, or explain code… <span>↑</span></div>}</section></main>;
}

interface WorkspaceProps {
  data: AppData;
  onChange: (workflow: Workflow) => void;
  onSave: (workflow: Workflow) => Promise<void>;
  onRunAction: (workflow: Workflow, action: "start" | "pause" | "resume" | "stop" | "reset", options?: SingleRunOptions) => Promise<void>;
  onSimulate: (workflow: Workflow, options?: SimulationOptions) => Promise<WorkflowSimulationReport>;
  onDelete: (workflow: Workflow) => Promise<void>;
  onConfigureRun: (workflow: Workflow, configuration: WorkflowRunConfiguration) => Promise<void>;
  onIntervene: (workflow: Workflow, input: CreateInterventionInput) => Promise<void>;
  onRespondToAttention: (workflow: Workflow, attentionId: string, input: RespondToAttentionInput) => Promise<void>;
  onOpenThread: (id: string) => void;
  onDesignerMessage: (workflow: Workflow, message: string) => Promise<void>;
  onUndo: (workflow: Workflow) => Promise<void>;
  designerSending: boolean;
  onGateDecision: (workflow: Workflow, nodeId: string, decision: "approve" | "decline") => Promise<void>;
}

function Workspace({ data, onChange, onSave, onRunAction, onSimulate, onDelete, onConfigureRun, onIntervene, onRespondToAttention, onOpenThread, onDesignerMessage, onUndo, designerSending, onGateDecision }: WorkspaceProps) {
  const { workflowId = "" } = useParams();
  const navigate = useNavigate();
  const workflow = data.workflows.find((item) => item.id === workflowId);
  const [selection, setSelection] = useState<Selection | null>(null);
  const [activityOpen, setActivityOpen] = useState(true);
  const [saving, setSaving] = useState(false);
  const [interventionOpen, setInterventionOpen] = useState(false);
  const [initialAttentionId, setInitialAttentionId] = useState<string>();
  const [editMode, setEditMode] = useState(false);
  const [mobilePane, setMobilePane] = useState<"chat" | "preview">("chat");
  const [deleting, setDeleting] = useState(false);
  if (!workflow) return <main className="workflow-missing"><h1>Workflow not found</h1><button onClick={() => navigate("/loop")}>Return to Loop</button></main>;

  const run = workflow.runs.at(-1);
  const runInactive = !run || ["stopped", "completed"].includes(run.status);
  const completed = workflow.nodes.filter((node) => node.status === "completed").length;
  const progress = workflow.nodes.length ? Math.round(workflow.nodes.reduce((sum, node) => sum + node.progress, 0) / workflow.nodes.length) : 0;
  const openAttentionRequests = workflow.attentionRequests.filter((request) => request.status === "open");
  const runAction = (action: "start" | "pause" | "resume" | "stop" | "reset", options?: SingleRunOptions) => onRunAction(workflow, action, options);
  const save = async () => { setSaving(true); try { await onSave(workflow); } finally { setSaving(false); } };
  const selectFromActivity = (nodeId: string) => setSelection({ type: "agent", id: nodeId });
  const activeAgents = workflow.threads.filter((thread) => ["starting", "running"].includes(thread.codex?.state ?? "")).length;
  const deleteLoop = async () => {
    if (!window.confirm(`Delete “${workflow.name}”? This removes the Loop definition and its run history. This cannot be undone.`)) return;
    setDeleting(true);
    try { await onDelete(workflow); } finally { setDeleting(false); }
  };

  retu
[truncated — 17451 more characters]
```

### src/components/inspector/index.ts

```typescript
export { SelectionInspector } from "./SelectionInspector";
export type { SelectionInspectorProps } from "./SelectionInspector";

```

[85 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]