diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..b4611ec --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "runloop", + "owner": { "name": "Runloop" }, + "metadata": { + "description": "Runloop: a local flight recorder for coding agents.", + "version": "0.1.0" + }, + "plugins": [ + { + "name": "runloop-tracer", + "source": "./integrations/claude-code-plugin", + "description": "Record Claude Code sessions into a local Runloop world and replay them. Observe-only.", + "version": "0.1.0", + "license": "Apache-2.0", + "category": "monitoring" + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b8df6d7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + # Installs the pnpm version pinned in package.json ("packageManager"). + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version-file: .nvmrc + cache: pnpm + + - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm test + - run: pnpm build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a2d645 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.runloop/ +.next/ +.next-*/ +out/ +coverage/ +*.tsbuildinfo +next-env.d.ts +.DS_Store +.env +.env.* diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..02b0696 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,60 @@ +# Changelog + +All notable changes to Runloop. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Runloop has not had a release yet. + +## [Unreleased] + +### Added +- **Claude Code tracer.** The plugin is in `integrations/claude-code-plugin` and the marketplace manifest in `.claude-plugin/marketplace.json`. + - Hooks are observe-only: async, with Stop, StopFailure and SessionEnd synchronous. They always exit 0 and print nothing. + - Events are spooled locally before sending, and flushed by later hooks. + - Client-side redaction, and a `--doctor` setup check. +- **Ingest.** `POST /api/ingest/claude-code`: + - guards: per-user token file, no browser `Origin`, 1 MiB body limit, per-session rate limit; + - dedupe by semantic keys; + - a pure, late-tolerant mapper; + - an idle sweep; + - continuation segments after a restart. +- **Event model** (schema v1, additive): + - traced runs (`mode`, `source`, an `external` adapter); + - `provenance` (`reported` / `inferred`) on the envelope; + - the `run.marker` and `agent.usage_reported` events; + - tool `category`; + - the `turn` and `delegated` task kinds; + - the `unobserved` failure type. +- **World.** Tool categories choose stations. Environments are named Atrium, Observatory, Workshop, Test Chamber and Launch Bay. There are presets for Claude and up to 8 subagent robots. +- **Studio: the Runloop Signature design system.** + - **Design:** dark and light tokens, self-hosted Geist fonts, a `src/ui/` component library and a command palette (⌘K / Ctrl-K). + - **Pages:** Overview, Live World, Traces, Integrations and Settings, with deep links (`?run=&view=&event=&agent=`). + - **Selection and search:** one selection shared by the world, timeline, inspector and URL; search and composable filters. + - **Run views:** a run metrics panel; a 2D Network graph and an agents table. + - **Traced runs:** traced badges, a provenance banner, inferred chips, and "not reported" values (never zero); live sessions open automatically. +- **3D world refresh:** + - all colours from tokens, so the world follows the theme; + - lime only for active execution; + - fit to viewport, with camera presets (overview, focus, next error); + - collision-free labels. +- **Tooling:** + - `scripts/screenshots.mjs` (`pnpm screenshots`) captures every page and key state in both themes at 1280, 1440, 1920 and 390 px. It adds `playwright@1.56.1` as a root devDependency. +- **Docs:** `docs/architecture/overview.md`, `docs/architecture/event-model.md` and `docs/status/verification-report.md`. + +### Changed +- The runtime's default port moved from 4317 to **4380**, because 4317 is the OTLP/gRPC port (DL-005). The Studio and the plugin default to 4380. +- Boot recovery is mode-aware. Traced runs close open calls as `unobserved` (inferred) rather than `runtime_restarted`. +- Control routes (`stop`, `pause`, `resume`) answer 409 for traced runs. +- Studio URLs end in a slash (`/overview/`), so the static export works on plain static servers. + +### Fixed +- `--doctor` now finds the plugin data dirs Claude Code actually uses (`runloop-tracer-inline` for `--plugin-dir`, and any other `runloop-tracer*` directory) when run from a shell. +- The Studio no longer shows "Runtime offline" after a single slow health check. It needs two consecutive failures once it has been online. +- In the 3D world, a selected incident label is no longer covered by the selected agent's label. A second required label shrinks before it may overlap. +- **Tracer delivery:** the hook sends the spool oldest first in size-bounded batches, its own event last, and stops at its time budget; a rejected batch is split so one bad envelope cannot block the rest. If the spool cannot be written, the event is sent directly. +- **Redaction:** more token shapes, credential headers and `key=value` pairs, URL passwords and shell password flags are masked, and each secret's literal value is masked wherever else it appears. Long strings are redacted before they are capped. Shell commands are stored as `sensitive`. +- **Tracer state:** the idle sweep measures silence by arrival time; a result arriving after its call was closed by a sweep is still recorded; permissions are closed as denied (inferred) at `PostToolBatch` or the agent's next batch rather than the next tool call; a late `PermissionRequest` after the result records the approval; subagents are linked to the delegating call by description and type (inferred), with a "Delegation corrected" marker if the guess was wrong. The ingest token file is checked for 0600. +- **Layer 1 performance:** the reducer folds batches in place (`transient`), tool calls live in a bucketed `IdMap`, and closing a call no longer scans every call; replay builds snapshots in linear time. +- **Incidents:** a traced failure is no longer resolved by the end of a turn. It resolves on a later successful test run or the same tool call succeeding again, labelled "Resolved (inferred)". +- **Studio replay:** stepping, seeking and deep links move by event, not by time, so events in the same millisecond can each be selected; `at=` keeps a paused playhead in the link. Late live events no longer move a paused replay. Refresh keeps the selected agent card. +- **Studio lists:** the timeline and logs are virtualised above 150 rows and are one Tab stop (arrow keys move between rows). A selected event hidden by a filter shows a "Show it" hint. Chips on a selected row keep AA contrast; tab focus rings are no longer clipped; the smallest text is 12 px. +- **Studio status:** an unfinished (interrupted) subagent shows "unfinished", not "working"; the inspector names the agent of the selected event; "Paused" is an info state; a finished run settles on its end without a spurious replay. +- **Network graph:** at narrow widths the graph uses smaller cards or a top-down layout instead of scaling the text down; a wrapped layer's sub-columns leave room for message labels. +- **Logs:** the agent column lines up on rows with and without chips. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7d0a3b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 The Runloop Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 3e7248c..c159772 100644 --- a/README.md +++ b/README.md @@ -1 +1,121 @@ -# runloop \ No newline at end of file +# Runloop + +A local flight recorder for coding agents. Runloop records every event of an agent run in an append-only log on your machine, shows the run live as a small 3D world with a timeline and an inspector, and replays any moment without executing anything again. + +![A live Claude Code session in Runloop Studio: the main agent and two Explore subagents as robots, the timeline and the run's provenance](docs/assets/claude-code-live.png) + +**Status: prototype.** It records two sources today: +- **Claude Code sessions**, traced through a plugin. Runloop records what Claude Code reports and never runs or approves anything itself. +- **A scripted demo mission**, executed by Runloop's own runtime with real tools. + +Run comparison (baselines and diffs) is the next milestone; see [TASKS.md](TASKS.md). + +## Quickstart + +You need Node 22.16 or newer and pnpm 10. + +```sh +pnpm install +pnpm dev # runtime API on http://127.0.0.1:4380, Studio on http://localhost:3000 +``` + +Open http://localhost:3000 and click **Run demo mission**. Three scripted agents survey a small repo, implement a task store and run its test suite. The first test run really fails (10 passed, 2 failed). The Builder then patches the bug, and the rerun passes 12/12. Click a timeline row or a robot to inspect its events. Drag the replay bar to see any earlier moment. + +Runs are stored in `.runloop/` at the repo root (set `RUNLOOP_DATA_DIR` to use another directory). They survive runtime restarts and replay from the log. + +## Trace Claude Code + +With the runtime running (`pnpm dev` or `pnpm serve`), start Claude Code with the plugin from this checkout: + +```sh +claude --plugin-dir ./integrations/claude-code-plugin +``` + +Or install it from the marketplace, inside Claude Code: + +``` +/plugin marketplace add argahv/runloop +/plugin install runloop-tracer@runloop +``` + +Install it only once: with both a checkout and a marketplace copy, every event is sent twice. + +Use Claude Code as usual. The session appears in Studio live, with: +- a robot for the main agent and one for each subagent (up to 8); +- tool calls at stations by kind (reading in the Observatory, edits and commands in the Workshop, tests in the Test Chamber); +- failing commands as incidents, with the reported exit code and output summary; +- subagent token usage as Claude Code reports it. The main agent's usage shows as "not reported", because hooks do not expose it. + +To check the setup, run `node integrations/claude-code-plugin/scripts/runloop-hook.mjs --doctor`. It prints the runtime URL, the token file, whether the runtime answers, the spool and the last error. + +| Claude Code session | Failing test as an incident | Network view (2D) | +|---|---|---| +| ![Live session](docs/assets/claude-code-live.png) | ![Incident and inspector](docs/assets/claude-code-failure.png) | ![Agent graph](docs/assets/claude-code-network.png) | + +These screenshots come from a real `claude -p` session (Haiku) traced end to end; see the [verification report](docs/status/verification-report.md). + +**What is collected.** The plugin sends these to the local runtime only: +- the session id; +- prompts and Claude's final replies (marked sensitive); +- tool names and summarised inputs; +- result sizes and counts, plus the tail of command output, used to read test counts; +- the model and token usage Claude Code reports for subagents; +- the project's base name (never its absolute path). + +File contents, diffs, full command output and transcripts are never collected. Well-known credential formats (API keys and tokens, URL passwords, `Authorization` headers, `password=…` style assignments, `curl -u`, `mysql -p`) and credential-like environment variables are masked in the plugin and again in the runtime; a secret in a form not on that list is stored as is, so shell commands are marked sensitive. The exact list, settings and opt-outs are in the [plugin README](integrations/claude-code-plugin/README.md). + +**Trust labels.** Every traced event is marked *reported* (Claude Code said it) or *inferred* (Runloop reconstructed it). An example of an inferred event is a denied permission, which fires no hook. The Studio shows these labels and never presents a traced result as verified. + +## Commands + +```sh +pnpm dev # runtime + Studio +pnpm serve # runtime only (port 4380; --port to change it) +pnpm demo # run the demo mission headless and print one trace line per event +pnpm test # unit, integration and end-to-end tests (vitest) +pnpm typecheck +pnpm build # static export of the Studio (apps/studio/out) +pnpm screenshots # visual regression screenshots of every page (needs a runtime and Studio running) +``` + +## How it works + +Three layers stay strictly apart: + +1. **Execution truth.** The append-only event log. Tool results come only from tools that really ran (executed runs) or from what the source reported (traced runs). +2. **World simulation.** A pure function of the log and a world definition: where each robot is and what it is doing. +3. **Presentation.** The Studio draws layer 2 and never writes back to it. + +Live view and replay fold the same events through the same reducers, so they always agree. More detail: +- [Architecture overview](docs/architecture/overview.md) +- [Event model](docs/architecture/event-model.md) +- [Claude Code tracer design](docs/plans/claude-code-tracer.md) +- [Design system](docs/design/design-system.md) +- [Current state](docs/status/current-state.md) + +## Security notes + +- The runtime listens on loopback only. Ingest from the plugin needs a per-user token (`~/.config/runloop/ingest-token`, created by the runtime) and rejects browser requests. +- **Read endpoints are not authenticated.** Any local process of yours can read recorded runs, including prompts and commands, just as it can read the database file. +- In the demo, `run_command` executes the workspace's tests as your user, without OS-level isolation. That is safe for the fixed demo code. Read the sandbox notes in [packages/runtime/README.md](packages/runtime/README.md) before connecting an adapter that writes its own code. + +## Repository layout + +``` +apps/studio Next.js Studio: Overview, Live World (3D + Network), Traces, Integrations, Settings +packages/events event schema, parsing, redaction, SequenceBuffer, HTTP/SSE and ingest contract +packages/core layer 1: execution reducer, selectors, text formatting +packages/world-engine layer 2: world definition, grid paths, motion, character intents +packages/replay combined reducer and ReplayIndex (state at any sequence or time) +packages/runtime local runtime: SQLite recorder, tool sandbox, run manager, Claude Code ingest, HTTP + SSE API, CLI +packages/testkit fixtures and helpers for tests +integrations/claude-code-plugin the Claude Code plugin (observe-only hooks, spool, --doctor) +worlds/research-campus the default world (rooms, stations, presets) +examples/coding-mission the demo mission and its example repo +scripts/screenshots.mjs visual regression screenshots (Playwright) +docs/ strategy, architecture, plans, design system, status +``` + +## License + +Apache-2.0. See [LICENSE](LICENSE). diff --git a/TASKS.md b/TASKS.md new file mode 100644 index 0000000..4494af2 --- /dev/null +++ b/TASKS.md @@ -0,0 +1,86 @@ +# Task ledger + +**States:** Not started · In progress · Blocked · Implemented, unverified · Verified · Deferred. + +A task is **Verified** only when its acceptance criteria were exercised and the evidence is recorded in `docs/status/current-state.md` or `docs/status/verification-report.md`. + +## Milestone A: Foundation + +| ID | Task | Status | Evidence / location | +|---|---|---|---| +| A1 | Event contract, Layer 1, Layer 2 and the replay engine | Verified | `packages/{events,core,world-engine,replay}`; 182 tests (2026-10-17, after the review fixes) | +| A2 | Runtime: recorder, sandboxed tools, API, SSE, recovery | Verified | `packages/runtime`; integration tests; manual SIGINT/SIGKILL | +| A3 | Studio with live view, replay and the 3D world | Verified (prototype quality) | `apps/studio`; Playwright screenshots | +| A4 | Move the runtime port 4317 → 4380 (DL-005) | Verified | `DEFAULT_RUNTIME_PORT = 4380` (events), Studio and plugin defaults; the B6 runtime ran on 4380 with no `--port` | +| A5 | Real command sandbox for `run_command` | Deferred | Only needed before untrusted adapters execute commands; documented in known limitations | + +## Milestone B: Claude Code tracer (the first real integration) + +Plan: `docs/plans/claude-code-tracer.md`. It depends on A4. + +| ID | Task | Status | Acceptance criteria | +|---|---|---|---| +| B0 | Probe real hook payloads (M0) | Verified | Two real `claude -p` sessions logged; findings in plan Appendix C | +| B1 | Schema: traced run mode, `external` adapter, provenance, `run.marker`, tool category, `turn`/`delegated` kinds, `unobserved` failure, usage events | Verified | Additive only; old logs parse; demo golden trace unchanged | +| B2 | Pure mapper (hook payload → event drafts) with golden tests from sanitised real fixtures | Verified | Every fixture folds with no unexpected anomalies; one terminal event per call; subagents attributed by `agent_id` | +| B3 | Ingest endpoint, token file, session manager, dedupe, idle sweep, mode-aware recovery, migration | Verified | 401 without token, 403 with an Origin, 413, idempotent retries, concurrent sessions in separate runs, scripted runs unaffected | +| B4 | Plugin (`integrations/claude-code-plugin`): async hooks, spool, `--doctor` | Verified | `claude plugin validate` passes; the script always exits 0 with empty stdout; offline spool then flush produces the same events | +| B5 | Studio: traced badge, provenance banner, inferred chips, live sessions, dynamic subagent robots, tool category → station | Verified | A traced session renders live; provenance is visible; every character is placed at a valid station | +| B6 | **End-to-end acceptance with real Claude Code** | Verified | A real `claude -p` session with the plugin produces a live run in Studio with the main agent plus subagent robots, a failing Bash test shown as an incident, survival across a page refresh, and replay without re-execution. An automated test covers the fixture path; the real path is recorded in the verification report | + +Still unverified with real sessions (outside B6's criteria): interactive approvals and denials, Esc interrupts, `/compact`, `/clear`, resume and the marketplace install. See `docs/status/current-state.md`. + +**Review fixes (2026-10-17).** 27 findings from the tracer, design and state reviews were fixed or narrowed; each is listed in `docs/status/verification-report.md` §5. They were verified with unit and fixture tests, synthetic hook runs and Studio screenshots, not with new real Claude Code sessions. B2–B4 stay Verified on that basis; the new permission and delegation inferences have only synthetic coverage. + +## Milestone C: Compare (the retention feature) + +| ID | Task | Status | Acceptance criteria | +|---|---|---|---| +| C1 | Pin a run as a baseline; pick any two runs to compare | Not started | Survives a restart (persisted) | +| C2 | Run diff: outcome, duration, tool calls by category, files touched, test results, tokens and cost where reported, permission waits, failures | Not started | Unit tests on the diff function; unknown values show as "not reported", never as zero | +| C3 | Compare view in Studio (side by side, with links to the differing events) | Not started | Clicking a difference opens the event in that run | + +## Milestone D: Runloop Signature design system and core observability + +Spec: `docs/design/design-system.md` (founder brief). This milestone runs in parallel with B; B5 is built on top of it. + +| ID | Task | Status | Acceptance criteria | +|---|---|---|---| +| D1 | Tokens (dark and light), Geist and Geist Mono self-hosted, tabular numerals, motion tokens, reduced motion | Verified | `tokens.css` is the only colour source; contrast checked at AA in both themes | +| D2 | Component library `src/ui/` (sidebar, tabs, split and resizable panels, command palette, chips, skeletons, empty, error, toast, confirm) | Verified | Keyboard reachable; used by every page; no colour-only state | +| D3 | IA: Overview · Live World · Traces · Integrations · Settings (Experiments and Evaluations hidden until they work); deep links | Verified | Each page has loading, empty and error states; deep link restores run, view, event or agent card, and a paused playhead (`at=`) | +| D4 | Synchronised selection across world, timeline and inspector; resizable inspector | Verified | Selecting in any view focuses the same entity in the others (tested in the store) | +| D5 | World refresh: Signature palette, named environments (Atrium, Observatory, Workshop, Test Chamber, Launch Bay), lime only for active execution, fit to viewport, no label collisions | Verified | No clipped rooms at 1280–1920 px; labels never overlap at default zoom; screenshots in both themes | +| D6 | 2D agent graph ("Network") view and a table fallback | Verified | Every world interaction has a 2D route | +| D7 | Search and filter in the timeline and logs (agent, tool, category, status, text) | Verified | Filters compose; URL-addressable; tested | +| D8 | Run metrics panel (duration, calls by category, failures, tokens and cost where reported) | Verified | Defined calculations; "not reported" never shown as zero | +| D9 | Visual regression screenshots of every page and state | Verified | Script in the repo; images checked on each milestone | +| D10 | World richness pass: the world fills its panel (tighter framing, collapsible timeline), robots read clearly at default zoom, warmer key/rim lighting and materials, restrained zone accent light; keep the Signature restraint | Not started | At 1440×900 a robot is ≥ 28 px tall at default zoom; the board fills ≥ 80% of the world panel width; founder-reference look (diorama, chibi robots) recognisable in screenshots of both themes | + +## Milestone E: Generalisation + +| ID | Task | Status | Acceptance criteria | +|---|---|---|---| +| E1 | Documented adapter contract and event-model docs | In progress | A new integration can be written from the docs alone | +| E2 | Codex CLI hooks integration (DL-004) | Not started | Real Codex session traced end to end, or fixture-verified if Codex cannot run here | +| E3 | Minimal TypeScript SDK for custom agents | Not started | An example agent emits a traced run | + +## Milestone F: Productisation + +| ID | Task | Status | +|---|---|---| +| F1 | Threat model, data-flow and retention docs; delete-run API | Not started | +| F2 | Contribution guide, security policy, changelog | In progress | +| F3 | Reproducible demo script (real Claude Code + scripted fallback, clearly labelled) | Not started | +| F5 | Marketing landing page in the Signature system ("Execution, made visible.") with a real recorded run | Not started | +| F4 | Performance measurements: ingest throughput, event-to-UI latency, large traces | In progress: first numbers on a synthetic 18.6k-event trace (labelled synthetic) in `docs/status/verification-report.md` §5; no CI benchmark, no real large session | + +## Deferred (with reason) + +| Item | Reason | +|---|---| +| Headless A/B runner (`runloop ab`) | Needs compare (C) first; costs real tokens per run | +| OTLP/HTTP ingest bridge | OTel GenAI conventions are still in development; build on demand | +| OpenAI Agents SDK / LangGraph adapters | Different user (agent builders); revisit when demand appears | +| Hosted sharing, teams, billing | No validated demand yet; see the monetisation hypothesis | +| Multi-agent simulation experiments | Keep simulation separate from captured traces; after compare | diff --git a/apps/studio/next.config.ts b/apps/studio/next.config.ts new file mode 100644 index 0000000..b16d6b5 --- /dev/null +++ b/apps/studio/next.config.ts @@ -0,0 +1,25 @@ +import path from 'node:path'; +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + output: 'export', + // Emit overview/index.html (not overview.html) so every static file server resolves the routes. + trailingSlash: true, + reactStrictMode: true, + // Don't (re)generate an AGENTS.md file on `next dev`. + agentRules: false, + // The dev badge would cover the sidebar's runtime status. + devIndicators: false, + transpilePackages: [ + '@runloop-world/core', + '@runloop-world/events', + '@runloop-world/replay', + '@runloop-world/world-engine', + ], + turbopack: { + root: path.join(import.meta.dirname, '../..'), + }, + images: { unoptimized: true }, +}; + +export default nextConfig; diff --git a/apps/studio/package.json b/apps/studio/package.json new file mode 100644 index 0000000..14dad15 --- /dev/null +++ b/apps/studio/package.json @@ -0,0 +1,35 @@ +{ + "name": "@runloop-world/studio", + "version": "0.1.0", + "private": true, + "license": "Apache-2.0", + "type": "module", + "scripts": { + "dev": "next dev -p 3000", + "build": "next build", + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "@react-three/drei": "10.7.9", + "@react-three/fiber": "9.8.2", + "@react-three/postprocessing": "3.2.0", + "@runloop-world/core": "workspace:*", + "@runloop-world/events": "workspace:*", + "@runloop-world/replay": "workspace:*", + "@runloop-world/world-engine": "workspace:*", + "geist": "1.8.1", + "next": "16.4.2", + "postprocessing": "6.39.5", + "react": "19.3.1", + "react-dom": "19.3.1", + "react-resizable-panels": "4.14.5", + "three": "0.186.1", + "zustand": "5.0.15" + }, + "devDependencies": { + "@runloop-world/testkit": "workspace:*", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "@types/three": "0.186.0" + } +} diff --git a/apps/studio/src/app/globals.css b/apps/studio/src/app/globals.css new file mode 100644 index 0000000..d93e5ef --- /dev/null +++ b/apps/studio/src/app/globals.css @@ -0,0 +1,134 @@ +/* Base styles. Colours, type, radii and motion come from tokens.css only. */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + height: 100%; +} + +body { + background: var(--rl-canvas); + color: var(--rl-text); + font-family: var(--rl-font-sans); + font-size: var(--rl-text-sm); + line-height: 1.5; + /* Tabular numerals everywhere: timestamps, sequences, durations and counts line up. */ + font-variant-numeric: tabular-nums; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +::selection { + background: var(--rl-selection); +} + +h1, +h2, +h3, +h4, +p, +ol, +ul, +dl, +dd, +figure { + margin: 0; +} + +ol, +ul { + padding: 0; + list-style: none; +} + +button, +input, +select, +textarea { + font: inherit; + color: inherit; + letter-spacing: inherit; +} + +button { + cursor: pointer; + background: none; + border: 0; + padding: 0; +} + +button:disabled { + cursor: not-allowed; +} + +a { + color: inherit; + text-decoration: none; +} + +:focus-visible { + outline: none; + box-shadow: var(--rl-focus-ring); +} + +code, +pre, +kbd, +samp { + font-family: var(--rl-font-mono); + /* Slightly smaller than the surrounding text, never below the 12 px floor. */ + font-size: max(0.92em, var(--rl-text-2xs)); +} + +pre { + margin: 0; +} + +/* Thin, quiet scrollbars. */ +* { + scrollbar-width: thin; + scrollbar-color: var(--rl-line-strong) transparent; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.mono { + font-family: var(--rl-font-mono); +} + +/* Reduced motion keeps every state change, only without the travel. */ +@media (prefers-reduced-motion: reduce) { + :root:not([data-motion='full']) *, + :root:not([data-motion='full']) *::before, + :root:not([data-motion='full']) *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } +} +:root[data-motion='reduce'] *, +:root[data-motion='reduce'] *::before, +:root[data-motion='reduce'] *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; +} diff --git a/apps/studio/src/app/integrations/page.tsx b/apps/studio/src/app/integrations/page.tsx new file mode 100644 index 0000000..dc5bc40 --- /dev/null +++ b/apps/studio/src/app/integrations/page.tsx @@ -0,0 +1,8 @@ +import type { Metadata } from 'next'; +import IntegrationsPage from '../../components/pages/IntegrationsPage'; + +export const metadata: Metadata = { title: 'Integrations' }; + +export default function Page() { + return ; +} diff --git a/apps/studio/src/app/layout.tsx b/apps/studio/src/app/layout.tsx new file mode 100644 index 0000000..dadad16 --- /dev/null +++ b/apps/studio/src/app/layout.tsx @@ -0,0 +1,36 @@ +import { GeistMono } from 'geist/font/mono'; +import { GeistSans } from 'geist/font/sans'; +import type { Metadata, Viewport } from 'next'; +import type { ReactNode } from 'react'; +import AppShell from '../components/AppShell'; +import { THEME_INIT_SCRIPT } from '../state/prefs'; +import './tokens.css'; +import './globals.css'; + +export const metadata: Metadata = { + title: { default: 'Runloop Studio', template: '%s · Runloop Studio' }, + description: 'Watch real agent work, inspect the trace, replay what happened.', +}; + +export const viewport: Viewport = { + colorScheme: 'dark light', + themeColor: [ + { media: '(prefers-color-scheme: dark)', color: '#101114' }, + { media: '(prefers-color-scheme: light)', color: '#f4f5f7' }, + ], +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + // Geist and Geist Mono are self-hosted by next/font (no network font fetches). + // data-theme is set by the inline script before first paint, so it differs from the server HTML. + return ( + + +