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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
file: docker/runtime/Dockerfile
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
Expand Down
19 changes: 4 additions & 15 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,4 @@
[package]
name = "multiagent"
version = "0.1.0"
edition = "2021"
rust-version = "1.98"
description = "Typed control plane for the Multiagent orchestration framework"
license = "MIT"

[dependencies]
chrono = { version = "0.4.45", default-features = false, features = ["clock"] }
fs2 = "0.4.3"
libc = "0.2.189"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.138"
sha2 = "0.10.9"
[workspace]
members = ["runtime"]
default-members = ["runtime"]
resolver = "2"
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Production operations are driven by authoritative Markdown runbooks rather than
Users are configured in a mounted JSON file. Passwords must be scrypt hashes, never plaintext:

```bash
node bin/hash-password.mjs operator
node control-server/bin/hash-password.mjs operator
```

The mounted file has this shape:
Expand Down Expand Up @@ -91,6 +91,21 @@ and authenticate at least one supported coding-agent CLI. Python 3.8+ is used
only by evaluation and evidence-analysis tools, not the production control
plane.

## Repository layout

- `client/` contains the independently distributed terminal client.
- `control-server/` contains the authenticated thread gateway.
- `runtime/` contains the Rust session runtime and supervisor package.
- `audit-log/` reserves the independent audit-service boundary for the next
implementation phase; phase 1 contains no audit-service behavior.
- `docker/` contains component image definitions and container entrypoints.
- `gitops/` documents the deployment integration boundary. Production GitOps
resources remain owned by the separate `InternalServices` repository.

Portable prompts, contracts, and runbook examples remain at the repository root
because they are shared framework artifacts rather than executable component
source.

## Quick Start

Run:
Expand Down
9 changes: 9 additions & 0 deletions audit-log/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Audit log service

This directory reserves an independent component boundary for the audit log
service planned for phase 2.

Phase 1 contains no audit-service executable, storage implementation, network
API, identity, signing authority, or image pipeline. Those behaviors require a
separate architecture decision and will be implemented only after the
repository-layout pull request is merged.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from "node:crypto";

const username = process.argv[2];
if (!username || !/^[a-zA-Z0-9._-]{1,64}$/.test(username)) {
console.error("usage: bin/hash-password.mjs USERNAME");
console.error("usage: control-server/bin/hash-password.mjs USERNAME");
process.exit(2);
}

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion control-server/test/prepare-repository.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { githubRepositoryFromUrl, issueAppJwt, issueInstallationToken, prepareRepository } from "../../bin/prepare-repository.mjs";
import { githubRepositoryFromUrl, issueAppJwt, issueInstallationToken, prepareRepository } from "../bin/prepare-repository.mjs";

test("GitHub repository URLs are parsed without accepting credentials or extra paths", () => {
assert.deepEqual(githubRepositoryFromUrl("https://github.com/MoveIndustries/sdk.git"), { owner: "MoveIndustries", repository: "sdk" });
Expand Down
14 changes: 14 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Container images

Component image definitions and their container-specific entrypoints live in
this directory. Build the current session-runtime/control-gateway image from the
repository root with:

```bash
docker build -f docker/runtime/Dockerfile -t multiagent:local .
```

The repository root remains the build context so the image can consume the
runtime package, control server, portable framework assets, and shared
contracts. Environment-specific deployment configuration is intentionally not
part of these image definitions.
6 changes: 4 additions & 2 deletions Dockerfile → docker/runtime/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM rust:1.98-bookworm AS multiagent-builder

WORKDIR /src
COPY . .
RUN cargo build --release --locked
RUN cargo build --release --locked --package multiagent

FROM node:22-bookworm-slim

Expand All @@ -19,7 +19,9 @@ COPY control-server/package*.json control-server/
RUN cd control-server && npm ci --omit=dev
COPY . .
COPY --from=multiagent-builder /src/target/release/multiagent /opt/multiagent/bin/multiagent
RUN chmod +x launch.sh bin/*.sh bin/*.mjs \
RUN install -m 0755 docker/runtime/container-entrypoint.sh /opt/multiagent/bin/container-entrypoint.sh \
&& install -m 0755 control-server/bin/prepare-repository.mjs /opt/multiagent/bin/prepare-repository.mjs \
&& chmod +x launch.sh control-server/bin/*.mjs \
&& groupadd --gid 10000 multiagent-control \
&& groupadd --gid 10001 multiagent-role \
&& groupadd --gid 10004 multiagent-credentials \
Expand Down
File renamed without changes.
File renamed without changes.
32 changes: 28 additions & 4 deletions docs/architecture/system-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ The deployment may also place a trusted repository-preparation init container
in front of a session runtime. That init container is not an agent and is not
part of the orchestrator's production-operation path.

## Repository component layout

Executable components and deployment integration surfaces have explicit
top-level ownership boundaries:

- `client/` owns the terminal client package.
- `control-server/` owns the authenticated control gateway package.
- `runtime/` owns the Rust session runtime, supervisor, and role-confinement
package.
- `audit-log/` is reserved for the independent audit service planned for a
later architecture and implementation phase. Its presence in the phase-one
layout grants it no authority and changes no trace behavior.
- `docker/` owns component image definitions and container entrypoints, but not
deployment secrets or environment-specific configuration.
- `gitops/` documents the application-to-deployment contract. Concrete GitOps
resources, identities, endpoints, storage, and secrets remain owned by the
separate `InternalServices` repository.

Portable prompts, contracts, and runbook examples remain shared framework
artifacts at the repository root. Directory placement must not be interpreted
as authority: the component ownership table and accepted architecture decisions
remain controlling.

## Accepted architecture decisions

### AD-001: The authenticated client user is the authorizing user
Expand Down Expand Up @@ -120,10 +143,11 @@ preventing client-only ID or lifecycle behavior from drifting from the server
contract.

The terminal-client implementation lives in the top-level `client/` package.
The `control-server/` package contains no client source or executable, and the
control-server container image excludes `client/`. This filesystem and package
boundary prevents the independently distributed caller from importing trusted
server internals; the public HTTP API is their only integration surface.
The `control-server/` package contains no client source or executable, the
session runtime implementation lives in the top-level `runtime/` package, and
the control-server container image excludes `client/`. These filesystem and
package boundaries prevent the independently distributed caller from importing
trusted server internals; the public HTTP API is its only integration surface.

### AD-002: There is one supervisor per execution session

Expand Down
5 changes: 3 additions & 2 deletions docs/technical-note.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,9 @@ no shared mutable state except declared artifacts.
scorer-only metadata outside every agent context.

This repository implements the snapshot primitive in
[`../src/snapshot.rs`](../src/snapshot.rs) and durable hash-bound finding/TODO
gate integration in [`../src/subagent.rs`](../src/subagent.rs). Benchmark
[`../runtime/src/snapshot.rs`](../runtime/src/snapshot.rs) and durable hash-bound
finding/TODO gate integration in
[`../runtime/src/subagent.rs`](../runtime/src/subagent.rs). Benchmark
adapters do not repeat these checks before submitting a workspace.

## Improvements over a single unconstrained agent loop
Expand Down
2 changes: 1 addition & 1 deletion evaluation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ authority reviewers, workers, verifiers, and final reviews. Build the exact
checkout before a live multiagent comparison:

```bash
docker build -t multiagent:ops-trace-current .
docker build -f docker/runtime/Dockerfile -t multiagent:ops-trace-current .
```

Override that image with `MULTIAGENT_OPS_TRACE_IMAGE`. The optional
Expand Down
3 changes: 2 additions & 1 deletion evaluation/adapters/ops_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ def _run_production_multiagent(
if inspected.returncode != 0:
raise RuntimeError(
f"production multiagent image is unavailable: {image}; "
"build it with `docker build -t multiagent:ops-trace-current .`"
"build it with `docker build -f docker/runtime/Dockerfile "
"-t multiagent:ops-trace-current .`"
)

runtime_root = Path(os.environ.get("MULTIAGENT_OPS_TRACE_RUNTIME_ROOT", "/tmp"))
Expand Down
6 changes: 4 additions & 2 deletions evaluation/swe_bench_pro_on_demand.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,10 @@ def _rust_builder_lines() -> list[str]:
"RUN apk add --no-cache musl-dev",
"WORKDIR /build",
"COPY multiagent/Cargo.toml multiagent/Cargo.lock ./",
"COPY multiagent/src ./src",
"RUN cargo build --release --locked",
"COPY multiagent/runtime/Cargo.toml runtime/Cargo.toml",
"COPY multiagent/runtime/src runtime/src",
"COPY multiagent/contracts contracts",
"RUN cargo build --release --locked --package multiagent",
]

@staticmethod
Expand Down
13 changes: 13 additions & 0 deletions gitops/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# GitOps integration boundary

This directory documents application-owned deployment contracts. It does not
contain the production GitOps source of truth.

The separate `InternalServices` repository owns Kubernetes resources, workload
identities, IAM, KMS, secrets, endpoints, storage, ingress, and concrete
runbook artifacts. Application code may define configuration interfaces and
image contracts here, but must not duplicate environment-specific deployment
configuration.

Phase 2 will update that external GitOps source together with the independent
audit-log image and service deployment.
2 changes: 1 addition & 1 deletion launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ command -v cargo >/dev/null 2>&1 || {
exit 1
}

exec cargo run --quiet --manifest-path "$SCRIPT_DIR/Cargo.toml" -- launch "$@"
exec cargo run --quiet --manifest-path "$SCRIPT_DIR/Cargo.toml" --package multiagent -- launch "$@"
15 changes: 15 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "multiagent"
version = "0.1.0"
edition = "2021"
rust-version = "1.98"
description = "Typed control plane for the Multiagent orchestration framework"
license = "MIT"

[dependencies]
chrono = { version = "0.4.45", default-features = false, features = ["clock"] }
fs2 = "0.4.3"
libc = "0.2.189"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.138"
sha2 = "0.10.9"
16 changes: 16 additions & 0 deletions runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Session runtime

This package contains the Rust `multiagent` binary, including the session
runtime, supervisor, role confinement, workflow state, and coding-agent backend
adapters.

Build and test it from the repository root through the Cargo workspace:

```bash
cargo build --locked --package multiagent
cargo test --locked --package multiagent
```

The runtime intentionally consumes portable framework assets from the
repository-level `prompts/`, `contracts/`, and `runbooks/` directories. Concrete
deployment configuration and credentials remain outside this package.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 4 additions & 3 deletions src/prod_ops.rs → runtime/src/prod_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1773,9 +1773,10 @@ mod tests {

#[test]
fn shared_action_permit_fixture_matches_the_rust_contract() {
let fixture: serde_json::Value =
serde_json::from_str(include_str!("../contracts/prod-mcp-action-permit-v1.json"))
.unwrap();
let fixture: serde_json::Value = serde_json::from_str(include_str!(
"../../contracts/prod-mcp-action-permit-v1.json"
))
.unwrap();
let request = fixture.get("request").unwrap();

assert_eq!(
Expand Down
File renamed without changes.
File renamed without changes.
8 changes: 6 additions & 2 deletions src/runtime.rs → runtime/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5179,8 +5179,12 @@ fn is_executable(path: &Path) -> bool {
}

fn framework_root() -> PathBuf {
env_path("MULTIAGENT_FRAMEWORK_ROOT")
.unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")))
env_path("MULTIAGENT_FRAMEWORK_ROOT").unwrap_or_else(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("runtime package must live below the framework root")
.to_path_buf()
})
}

fn env_nonempty(key: &str) -> Option<String> {
Expand Down
2 changes: 1 addition & 1 deletion src/session_control.rs → runtime/src/session_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ mod tests {
#[test]
fn session_ids_match_the_shared_control_plane_contract() {
let vectors: serde_json::Value =
serde_json::from_str(include_str!("../contracts/session-id-vectors.json")).unwrap();
serde_json::from_str(include_str!("../../contracts/session-id-vectors.json")).unwrap();
for value in vectors["valid"].as_array().unwrap() {
assert!(validate_session_id(value.as_str().unwrap()).is_ok());
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ assert_file_contains "$ROOT/runbooks/github-repository-work.md" "explicitly auth
assert_file_contains "$ROOT/evaluation/README.md" "large-update-300"
assert_file_contains "$ROOT/evaluation/README.md" "Low-signal orchestration cases"
assert_file_contains "$ROOT/orchestrator_prompt.md" "MULTIAGENT_PROMPT_MODULE_ROOT"
assert_file_contains "$ROOT/src/runtime.rs" "MULTIAGENT_PROMPT_MODULE_ROOT"
assert_file_contains "$ROOT/runtime/src/runtime.rs" "MULTIAGENT_PROMPT_MODULE_ROOT"
assert_file_not_contains "$ROOT/launch.sh" "python"
assert_file_contains "$ROOT/prompts/verifier.md" "state-space partition audit"
assert_file_contains "$ROOT/prompts/verifier.md" "mixed-category, unknown/forward-compatible variant"
Expand Down Expand Up @@ -1170,7 +1170,7 @@ assert_file_contains "$ROOT/prompts/contracts/orchestration-invariants.md" "prom
assert_file_contains "$ROOT/prompts/contracts/orchestration-invariants.md" "build-verification-passed:"
assert_file_contains "$ROOT/prompts/playbooks/finding-todo-loop.md" "Do not create or reopen a todo from command evidence bound"
assert_file_contains "$MULTIAGENT" subagent '--own|--owned-path)'
assert_file_contains "$ROOT/src/runtime.rs" 'crate::snapshot::canonical_diff(&cfg.root, "HEAD")'
assert_file_contains "$ROOT/runtime/src/runtime.rs" 'crate::snapshot::canonical_diff(&cfg.root, "HEAD")'
assert_file_contains "$MULTIAGENT" subagent '--source-finding-id|--finding)'
assert_file_contains "$MULTIAGENT" subagent '--role)'
assert_file_contains "$ROOT/prompts/roles/acceptance-scout.md" "declared-type ownership risk"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_container_runtime_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

class ContainerRuntimeContractTests(unittest.TestCase):
def test_session_bootstrap_does_not_read_trace_archive(self):
entrypoint = (ROOT / "bin/container-entrypoint.sh").read_text()
entrypoint = (ROOT / "docker/runtime/container-entrypoint.sh").read_text()
self.assertNotIn("MULTIAGENT_STATE_S3_URI", entrypoint)
self.assertNotIn("aws s3 sync", entrypoint)

def test_state_parent_is_traversable_by_isolated_roles_but_not_writable(self):
runtime = (ROOT / "src/runtime.rs").read_text()
runtime = (ROOT / "runtime/src/runtime.rs").read_text()
state_entry = runtime.split('base.join("state")', 1)[1].split("),", 1)[0]
self.assertIn("config::ROLE_GID", state_entry)
self.assertIn("0o2750", state_entry)
Expand Down
7 changes: 5 additions & 2 deletions tests/test_migration_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ class MigrationCliContractTest(unittest.TestCase):
def test_launch_is_the_only_production_shell_bootstrap(self):
self.assertTrue((PROJECT_ROOT / "launch.sh").is_file())
self.assertEqual(
list((PROJECT_ROOT / "bin").glob("*.sh")),
[PROJECT_ROOT / "bin" / "container-entrypoint.sh"],
list(PROJECT_ROOT.glob("*.sh")),
[PROJECT_ROOT / "launch.sh"],
)
self.assertTrue(
(PROJECT_ROOT / "docker" / "runtime" / "container-entrypoint.sh").is_file()
)
launch = (PROJECT_ROOT / "launch.sh").read_text(encoding="utf-8")
self.assertIn('exec "$MULTIAGENT_BIN" launch "$@"', launch)
Expand Down
5 changes: 4 additions & 1 deletion tests/test_native_solver_import_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ def test_bake_copies_package_initializers(self) -> None:
manager._rust_builder_lines()[0],
"FROM rust:1.85-alpine AS multiagent-builder",
)
self.assertIn("RUN cargo build --release --locked", manager._rust_builder_lines())
self.assertIn(
"RUN cargo build --release --locked --package multiagent",
manager._rust_builder_lines(),
)

def test_native_modules_have_strict_relative_imports(self) -> None:
failures = []
Expand Down
Loading