Skip to content
Draft
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
92 changes: 92 additions & 0 deletions .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// @ts-check
// Deep-module enforcement for dependency-cruiser.
//
// Each package under the packages root is a DEEP MODULE: a lot of behaviour
// behind a small interface. A package's PUBLIC SURFACE is its ENTRY POINT:
// src/index.ts, re-exported through the package manifest's "." export.
// Everything else in src/ is private implementation. Apps (in apps/) are not
// packages: they import packages through their entry points and each other
// not at all.
//
// The only thing you should ever need to edit here is PACKAGES_ROOT.

/** Where packages live. One immediate child dir per package (flat, no nesting). */
const PACKAGES_ROOT = "packages";

// --- derived patterns (no need to edit) -------------------------------------
const R = PACKAGES_ROOT;
/**
* A package's private internals: anything inside src/ except the entry
* point itself. src/index.ts is NOT matched (it stays importable from
* outside); every other file in src/ IS matched, at root level or nested.
*/
const PACKAGE_INTERNALS = `^${R}/[^/]+/src/(?!index\\.ts$).+`;

/** Test folders: a package's test/ directory. */
const PACKAGE_TESTS = `^${R}/([^/]+)/tests?/`;
const PACKAGE_OWN_TESTS = `^${R}/$1/tests?/`;

/** @type {import('dependency-cruiser').IConfiguration} */
module.exports = {
forbidden: [
{
name: "entrypoint-boundary-from-app",
comment:
"App/root code may import a package's entry point (its src/index.ts, re-exported as the package root), but nothing else inside the package.",
severity: "error",
from: { pathNot: `^${R}/` }, // importer is NOT inside any package
to: { path: PACKAGE_INTERNALS },
},
{
name: "entrypoint-boundary-across-packages",
comment:
"A package's own files import each other freely, but may reach OTHER packages only through their entry points, never their internals.",
severity: "error",
// importer is inside a package ($1), but is not a test file
from: { path: `^${R}/([^/]+)/`, pathNot: PACKAGE_TESTS },
to: {
path: PACKAGE_INTERNALS,
pathNot: `^${R}/$1/`, // same package → intra-package freedom
},
},
{
name: "tests-through-entrypoints",
comment:
"A package's tests exercise it through its entry points like everyone else: they may import any package's entry points and their own test/ fixtures, but never any package's internals, not even their own.",
severity: "error",
from: { path: `^${R}/([^/]+)/tests?/` }, // a test file, in package $1
to: {
path: PACKAGE_INTERNALS,
pathNot: PACKAGE_OWN_TESTS, // own tests/ fixtures → allowed
},
},
{
name: "tests-folder-is-private",
comment:
"A package's tests/ folder is reachable only from tests: nothing else may import fixtures.",
severity: "error",
from: { pathNot: PACKAGE_TESTS }, // importer is not itself a test
to: { path: `^${R}/[^/]+/tests?/` },
},
{
name: "no-circular",
comment: "No dependency cycles.",
severity: "error",
from: {},
to: { circular: true },
},
],
options: {
// TypeScript 7 has no stable API; parse with SWC so .ts files are actually
// cruised (without this depcruise silently skips them).
parser: "swc",
doNotFollow: { path: "node_modules" },
exclude: {
path: "(^|/)(node_modules|dist|target|\\.direnv|\\.alchemy)/",
},
tsConfig: { fileName: "tsconfig.json" },
enhancedResolveOptions: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
},
},
};
4 changes: 4 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- uses: dtolnay/rust-toolchain@stable
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run prepare
- run: bun run check
- run: bun run test
- run: bun run lint
- run: bun run fmt:check
- run: cargo check --workspace
- run: cargo test --workspace
- run: cargo fmt --all -- --check
- uses: DeterminateSystems/determinate-nix-action@v3
- run: nix flake check
- run: docker compose -f ops/compose/local.yaml config
8 changes: 7 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 80,
"ignorePatterns": ["**/*.sops.yaml"],
"ignorePatterns": [
"**/*.sops.yaml",
"**/routeTree.gen.ts",
".agents/**",
"packages/tooling/src/secrets.yaml",
"vendor/**"
],
"sortPackageJson": true
}
94 changes: 64 additions & 30 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
# Darkmatter Production-Ready Template
# Darkmatter Effect Agent Harness Template

Bun + Effect web application with a deliberately structured operational
surface. This is the org reference for the preferred TypeScript toolchain
(Bun, tsgo, oxlint/oxfmt) and a Nix flake-parts + Prelude devshell, without
treating `ops/` as a junk drawer.
Bun + Effect agent harness with a Tauri desktop shell, a bounded Rust process
supervisor, and a deliberately structured operational surface. This is the org
reference for clean Effect-native TypeScript and the preferred toolchain (Bun,
tsgo, oxlint/oxfmt, Nix flake-parts + Prelude).

## Repository layout

| Path | Purpose |
| -------------------- | ---------------------------------------------------------------- |
| `apps/web/` | Demo web app (`@ops-demo/web`) — Effect/Bun HTTP server |
| `packages/web-core/` | Framework-independent domain logic (`@repo/web-core`) |
| `packages/tooling/` | Shared TypeScript and Oxc configuration (`@repo/tooling`) |
| `flake.nix` | Root flake — stays at root because Nix discovers flakes there |
| `flake/` | Thin public Nix-output layer (apps, checks, devShells, packages) |
| `nix/demo/` | Nix package and smoke-check implementation |
| `nix/prelude.nix` | Prelude command catalogue (`x` menu, MOTD, docs) |
| `ops/` | Operational surface — see [ops/README.md](ops/README.md) |
| `tests/` | Cross-package smoke tests |
| `docs/` | Architecture and getting-started docs |
| `.github/workflows/` | CI pipeline |
| Path | Purpose |
| -------------------------- | ---------------------------------------------------------------- |
| `apps/cli/` | Effect CLI and terminal rendering boundary |
| `apps/harnessd/` | Typed harness HTTP API and long-lived Bun server |
| `apps/web/` | Status/architecture page (`@agent-demo/web`) — Effect/Bun server |
| `apps/native/` | Tauri shell and managed Effect runtime boundary |
| `packages/agent-core/` | Stable schemas, services, journal, and agent loop |
| `packages/agent-demo/` | Provider-free model/tool layers shared by runnable apps |
| `packages/agent-runtime/` | Effect AI and sandbox adapters |
| `packages/agent-testkit/` | Scripted model and deterministic test layers |
| `packages/sandbox-client/` | Schema contract for the Rust supervisor |
| `packages/web-core/` | Framework-independent status and metrics helpers |
| `packages/tooling/` | Shared TypeScript and Oxc configuration |
| `crates/agent-sandboxd/` | Bounded NDJSON process supervisor |
| `flake.nix` | Root flake — stays at root because Nix discovers flakes there |
| `flake/` | Thin public Nix-output layer (apps, checks, devShells, packages) |
| `nix/demo/` | Nix package and smoke-check implementation |
| `nix/prelude.nix` | Prelude command catalogue (`x` menu, MOTD, docs) |
| `ops/` | Operational surface — see [ops/README.md](ops/README.md) |
| `tests/` | Cross-package smoke tests |
| `docs/` | Architecture and getting-started docs |
| `.github/workflows/` | CI pipeline |

### `ops/` boundary

Expand Down Expand Up @@ -55,9 +64,10 @@ application schema still belongs beside the application that uses it.
Split files that exceed this.
- oxfmt is configured at 80 print width and sorts `package.json` keys.
Prettier is disabled in Zed — oxfmt is the only formatter.
- The root `bun run check` runs `tsc` then per-package `tsc --noEmit` for
`@ops-demo/web` and `@repo/web-core`. New packages with a `typecheck`
script should be added to this chain.
- The root `bun run check` runs root `tsc` then every workspace package's
`typecheck` script through Bun's workspace filter.
- Rust uses the root Cargo workspace. Run `cargo check --workspace` and
`cargo test --workspace` after changing the supervisor or its protocol.

### Nix devshell

Expand All @@ -66,6 +76,8 @@ automatic entry). Inside the shell:

- `x` — interactive command picker
- `x dev` — run the Effect/Bun demo server
- `x cli` — run the deterministic harness CLI
- `x harnessd` — run the typed harness daemon
- `x check` — tsgo typecheck
- `x test` — Vitest
- `x lint` — oxlint
Expand Down Expand Up @@ -95,12 +107,29 @@ After changing `package.json` dependencies, regenerate the Nix lock:

## Application architecture

The application is intentionally small. `apps/web` serves a static front
page, a `/api/status` endpoint, and Prometheus-compatible `/api/metrics`
through Effect and Bun (`@effect/platform-bun`).
The demo is an Effect-native agent harness. `packages/agent-core` owns branded
IDs, schema-backed requests/decisions/results/events/errors, capability
services, an in-memory event journal, and the bounded sequential agent loop.
`AgentHarness.layerNoDeps` captures its model, tool, journal, and configuration
dependencies so its public operation has no hidden environment requirement.

Domain helpers live in `packages/web-core` so they can be tested without
the HTTP server:
Provider and process details point inward from adapters. Only
`packages/agent-runtime` may import `effect/unstable/ai`.
`packages/sandbox-client` owns the tagged wire protocol implemented by
`crates/agent-sandboxd`. The Rust daemon supervises processes but is not a
security sandbox.

`packages/agent-demo` supplies one deterministic, schema-validated tool
round-trip without provider credentials. `apps/cli`, `apps/harnessd`, and
`apps/native` consume this same layer; application entrypoints must not copy or
reimplement the harness loop.

`apps/native` creates one `ManagedRuntime` for UI callback execution and
disposes it with the application lifecycle. `apps/web` remains the operational
shell, serving a static architecture page, `/api/status`, and Prometheus-ready
`/api/metrics` through Effect and Bun.

Web support helpers live in `packages/web-core`:

- `AppConfig` — Effect service reading `HOST`, `PORT`, `APP_ENV`,
`APP_RELEASE`, and the optional redacted `DEMO_MESSAGE`; the web adapter can
Expand All @@ -121,8 +150,10 @@ image reference. Production uses a digest rather than a mutable image tag.

## Testing

- Unit tests live next to the code they test (`packages/web-core/test/`).
They use `@effect/vitest` with `it.effect` and test layers.
- Unit tests live next to the code they test. They use `@effect/vitest` with
`it.effect` and fresh layers; do not wrap `it.effect` in `Effect.scoped`.
- `packages/agent-testkit` replaces model and tool services with deterministic
layers while exercising the real harness and journal.
- Smoke tests live in `tests/` and spawn the real server to verify the
status API and front page end-to-end.
- Test files match `**/*.test.ts`. Vitest excludes `.direnv/**`.
Expand Down Expand Up @@ -150,8 +181,9 @@ image reference. Production uses a digest rather than a mutable image tag.
4. `bun run test`
5. `bun run lint`
6. `bun run fmt:check`
7. `nix flake check`
8. `docker compose -f ops/compose/local.yaml config`
7. `cargo check --workspace`, `cargo test --workspace`, and Rust formatting
8. `nix flake check`
9. `docker compose -f ops/compose/local.yaml config`

All steps must pass. The `--ignore-scripts` install flag means no
postinstall scripts run in CI — the `prepare` step handles patching.
Expand All @@ -165,6 +197,8 @@ bun run check
bun run test
bun run lint
bun run fmt:check
cargo check --workspace
cargo test --workspace
nix flake check
docker compose -f ops/compose/local.yaml config
```
Expand Down
114 changes: 114 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[workspace]
members = ["crates/agent-sandboxd"]
exclude = ["apps/native/src-tauri"]
resolver = "2"

[workspace.package]
edition = "2021"
license = "MIT"
rust-version = "1.77.2"

[workspace.dependencies]
libc = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Loading
Loading