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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ OPENROUTER_API_KEY=replace-with-your-openrouter-key

# The scripted provider is exclusively for deterministic automation. Normal
# interactive development must leave this unset so every turn uses OpenRouter.
# HEXZERO_PROVIDER=scripted
# Deprecated compatibility alias (HEXZERO_PROVIDER takes precedence):
# AGENTBORNE_PROVIDER=scripted

# Optional local experiment archive path.
# HEXZERO_EXPERIMENT_DB=.hexzero/experiments.sqlite
# Deprecated compatibility alias (HEXZERO_EXPERIMENT_DB takes precedence):
# AGENTBORNE_EXPERIMENT_DB=.agentborne/experiments.sqlite

# Development-map center (Toledo, Ohio) and H3 resolution.
NEXT_PUBLIC_DEV_MAP_LATITUDE=41.6528
NEXT_PUBLIC_DEV_MAP_LONGITUDE=-83.5379
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ test-results/
*.log
logs/
.agentborne/
.hexzero/
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ These instructions apply throughout the repository.

## Product boundaries

- Treat “Agentborne” as a working title. Avoid heavy branding, logo, lore, or name-specific design investment.
- Use “Hex Zero” consistently while avoiding unnecessary logo, lore, or name-specific design investment.
- Keep the World Lab as a production developer/admin surface, not a disposable demo.
- The only initial hex states are `open` and `infected`.
- The only agent world actions are adjacent move, infect current cell, capture an abandoned infected current cell from a non-allied controller, and wait. One optional public or range-limited direct communication and one optional formal diplomacy intent may accompany that action.
Expand Down
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agentborne
# Hex Zero

Agentborne is the working title for an agent-first geographic experiment. A configurable roster of model-backed agents moves, infects, captures contested territory, communicates, and conducts formal alliance diplomacy while the World Lab exposes every safe decision record.
Hex Zero is an agent-first geographic experiment. A configurable roster of model-backed agents moves, infects, captures contested territory, communicates, and conducts formal alliance diplomacy while the World Lab exposes every safe decision record.

Scenarios may optionally designate one experimental Patient Zero coordinator.
The role receives bounded global strategic information and can send private
Expand Down Expand Up @@ -31,6 +31,10 @@ pnpm install --frozen-lockfile
pnpm dev
```

For deterministic local automation, `pnpm dev:test-provider` sets
`HEXZERO_PROVIDER=scripted`. `HEXZERO_EXPERIMENT_DB` overrides the local
experiment archive path.

Open the World Lab at <http://localhost:3000>. The Game API binds to <http://127.0.0.1:8787>; Next.js narrowly proxies `/api/game/*` to it. `OPENROUTER_API_KEY` is the only required OpenRouter environment value. Select a compatible global model in World Lab, then optionally override individual agents. Each assignment may use the provider's default reasoning behavior, disable optional reasoning, or select only an effort advertised by that model's catalog metadata.

Each Single turn or completed playback interval makes one third-party OpenRouter request and may incur cost. Start is deliberately disabled when the server has no key. This development API has no authentication or cost controls and is not suitable for an unauthenticated public deployment.
Expand Down Expand Up @@ -66,3 +70,24 @@ The agent roster defaults to browser-local **Follow turn** behavior: the inspect
See [Testing](docs/TESTING.md), [Architecture](docs/ARCHITECTURE.md), [Security](docs/SECURITY.md), the accepted future [Gameplay Foundation](docs/GAMEPLAY_FOUNDATION.md), and the [Roadmap](ROADMAP.md).

Completed schema-v9 exports can be imported into an ignored local SQLite archive and queried without repeatedly loading full JSON artifacts. See [Local experiment archive](docs/EXPERIMENT_ARCHIVE.md).

## Rename compatibility

Hex Zero was formerly named Agentborne. Workspace packages now use the
`@hexzero/*` namespace, and the repository URL will be
`https://github.com/BlackSwampAI/hexzero` after the external repository rename.
Existing environments may continue using `AGENTBORNE_PROVIDER` and
`AGENTBORNE_EXPERIMENT_DB`; the corresponding `HEXZERO_` variable takes
precedence, and selecting a legacy alias emits a value-free deprecation notice.

New archives default to `.hexzero/experiments.sqlite`. When that file does not
exist, an existing `.agentborne/experiments.sqlite` is opened in place with a
migration notice; Hex Zero never moves or overwrites it automatically. To
migrate manually, stop every Hex Zero process, create `.hexzero`, copy the
legacy database (including any `-wal` and `-shm` sidecars if present), verify
the copy opens, and only then remove the legacy files if desired.

New downloads use `hexzero-experiment-`. Existing
`agentborne-experiment-*.json` files and their schema-v9 contents remain
importable unchanged. Browser-owned settings stored under legacy `agentborne`
keys are schema-validated and copied once to the new `hexzero` keys.
8 changes: 4 additions & 4 deletions apps/game-api/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@agentborne/game-api",
"name": "@hexzero/game-api",
"version": "0.0.0",
"private": true,
"type": "module",
Expand All @@ -12,9 +12,9 @@
"typecheck": "tsc -p tsconfig.json"
},
"dependencies": {
"@agentborne/agent-runtime": "workspace:*",
"@agentborne/shared": "workspace:*",
"@agentborne/world-engine": "workspace:*",
"@hexzero/agent-runtime": "workspace:*",
"@hexzero/shared": "workspace:*",
"@hexzero/world-engine": "workspace:*",
"@hono/node-server": "2.1.0",
"h3-js": "4.5.0",
"hono": "4.13.2"
Expand Down
49 changes: 44 additions & 5 deletions apps/game-api/src/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import {
AgentProviderError,
BrowserTestAgentProvider,
OpenRouterAgentProvider,
ScriptedAgentProvider,
type AgentProvider,
type ProviderDecision,
} from '@agentborne/agent-runtime';
} from '@hexzero/agent-runtime';
import {
cancelledTurnResponseSchema,
h3CellSchema,
Expand All @@ -20,8 +21,46 @@ import {
updateAgentPersonalityResponseSchema,
updateExperimentModelsResponseSchema,
verifyModelResponseSchema,
} from '@agentborne/shared';
import { createApp } from './app';
} from '@hexzero/shared';
import {
createApp,
providerFromEnvironment,
resolveProviderModeFromEnvironment,
} from './app';

describe('provider environment compatibility', () => {
it('uses the canonical provider variable without a warning', () => {
const warn = vi.fn();
expect(
providerFromEnvironment({ HEXZERO_PROVIDER: 'scripted' }, warn),
).toBeInstanceOf(BrowserTestAgentProvider);
expect(warn).not.toHaveBeenCalled();
});

it('prefers the canonical provider variable over the legacy alias', () => {
const warn = vi.fn();
expect(
resolveProviderModeFromEnvironment(
{
HEXZERO_PROVIDER: 'openrouter',
AGENTBORNE_PROVIDER: 'scripted',
},
warn,
),
).toBe('openrouter');
expect(warn).not.toHaveBeenCalled();
});

it('supports the legacy provider alias with a value-free warning', () => {
const warn = vi.fn();
expect(
providerFromEnvironment({ AGENTBORNE_PROVIDER: 'scripted' }, warn),
).toBeInstanceOf(BrowserTestAgentProvider);
expect(warn).toHaveBeenCalledWith(
'AGENTBORNE_PROVIDER is deprecated; use HEXZERO_PROVIDER. Continuing with the legacy setting.',
);
});
});

describe('game API simulation boundary', () => {
it('coalesces repeated delivery of the same turn mutation ID', async () => {
Expand All @@ -48,7 +87,7 @@ describe('game API simulation boundary', () => {
const request = () =>
app.request('/api/simulation/turn', {
method: 'POST',
headers: { 'X-Agentborne-Mutation-Id': 'mutation_same_001' },
headers: { 'X-Hexzero-Mutation-Id': 'mutation_same_001' },
});
const [first, duplicate] = await Promise.all([request(), request()]);
expect(first.status).toBe(200);
Expand Down
34 changes: 26 additions & 8 deletions apps/game-api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
OpenRouterModelCatalog,
OpenRouterAgentProvider,
type AgentProvider,
} from '@agentborne/agent-runtime';
} from '@hexzero/agent-runtime';
import {
apiErrorSchema,
AGENT_DECISION_CONTRACT_VERSION,
Expand Down Expand Up @@ -43,11 +43,11 @@ import {
locationSearchResponseSchema,
defaultWorldSetupResponseSchema,
type ModelVerification,
} from '@agentborne/shared';
} from '@hexzero/shared';
import {
createDevelopmentWorld,
generateDeterministicRoster,
} from '@agentborne/world-engine';
} from '@hexzero/world-engine';
import {
SimulationConflictError,
SimulationService,
Expand All @@ -66,12 +66,30 @@ export interface AppOptions {
geocoder?: Geocoder;
}

export function providerFromEnvironment(): AgentProvider {
if (process.env.AGENTBORNE_PROVIDER === 'scripted') {
export function resolveProviderModeFromEnvironment(
environment: NodeJS.ProcessEnv = process.env,
warn: (message: string) => void = console.warn,
): string | undefined {
if (environment.HEXZERO_PROVIDER !== undefined)
return environment.HEXZERO_PROVIDER;
if (environment.AGENTBORNE_PROVIDER !== undefined) {
warn(
'AGENTBORNE_PROVIDER is deprecated; use HEXZERO_PROVIDER. Continuing with the legacy setting.',
);
return environment.AGENTBORNE_PROVIDER;
}
return undefined;
}

export function providerFromEnvironment(
environment: NodeJS.ProcessEnv = process.env,
warn: (message: string) => void = console.warn,
): AgentProvider {
if (resolveProviderModeFromEnvironment(environment, warn) === 'scripted') {
return new BrowserTestAgentProvider();
}
return new OpenRouterAgentProvider({
apiKey: process.env.OPENROUTER_API_KEY,
apiKey: environment.OPENROUTER_API_KEY,
});
}

Expand All @@ -95,7 +113,7 @@ export function createApp(options: AppOptions = {}) {
execute: () => Promise<T>,
): Promise<T> => {
const supplied =
context.req.header('x-agentborne-mutation-id') ??
context.req.header('x-hexzero-mutation-id') ??
context.req.query('mutationId');
const mutationId =
supplied && /^[A-Za-z0-9_-]{8,80}$/.test(supplied) ? supplied : undefined;
Expand All @@ -119,7 +137,7 @@ export function createApp(options: AppOptions = {}) {
? origin
: null,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'X-Agentborne-Mutation-Id'],
allowHeaders: ['Content-Type', 'X-Hexzero-Mutation-Id'],
}),
);

Expand Down
2 changes: 1 addition & 1 deletion apps/game-api/src/experiment-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
type WorldSnapshot,
type BehaviorConfiguration,
type AppliedScenario,
} from '@agentborne/shared';
} from '@hexzero/shared';

export interface ExperimentSource {
id: ExperimentId;
Expand Down
4 changes: 2 additions & 2 deletions apps/game-api/src/geocoder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
locationSearchResponseSchema,
type LocationSearchResponse,
} from '@agentborne/shared';
} from '@hexzero/shared';

export interface Geocoder {
search(query: string): Promise<LocationSearchResponse>;
Expand Down Expand Up @@ -43,7 +43,7 @@ export class NominatimGeocoder implements Geocoder {
const response = await fetch(url, {
headers: {
'User-Agent':
'Agentborne-WorldLab (+https://github.com/BlackSwampAI/agentborne)',
'Hex Zero-WorldLab (+https://github.com/BlackSwampAI/hexzero)',
Accept: 'application/json',
},
signal: controller.signal,
Expand Down
7 changes: 5 additions & 2 deletions apps/game-api/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { readFileSync } from 'node:fs';
import { serve } from '@hono/node-server';
import { applyProviderEnvironmentFile } from '@agentborne/agent-runtime';
import { applyProviderEnvironmentFile } from '@hexzero/agent-runtime';
import { createApp } from './app';

if (process.env.AGENTBORNE_PROVIDER !== 'scripted') {
if (
(process.env.HEXZERO_PROVIDER ?? process.env.AGENTBORNE_PROVIDER) !==
'scripted'
) {
try {
process.loadEnvFile('../../.env');
applyProviderEnvironmentFile(
Expand Down
6 changes: 3 additions & 3 deletions apps/game-api/src/simulation-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
ScriptedAgentProvider,
type AgentProvider,
type ProviderDecision,
} from '@agentborne/agent-runtime';
} from '@hexzero/agent-runtime';
import {
PERSONALITY_MAX_LENGTH,
agentIdSchema,
Expand All @@ -16,12 +16,12 @@ import {
type AgentTurnRecord,
type CompatibleModel,
type WorldEvent,
} from '@agentborne/shared';
} from '@hexzero/shared';
import {
DEVELOPMENT_AGENT_BLUEPRINTS,
defaultWorldSetupRequest,
physicalDistanceKm,
} from '@agentborne/world-engine';
} from '@hexzero/world-engine';
import {
SimulationConflictError,
SimulationService,
Expand Down
6 changes: 3 additions & 3 deletions apps/game-api/src/simulation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
AgentProviderError,
type AgentProvider,
type ProviderDecision,
} from '@agentborne/agent-runtime';
} from '@hexzero/agent-runtime';
import {
agentIdSchema,
appliedScenarioSchema,
Expand Down Expand Up @@ -57,7 +57,7 @@ import {
type AppliedScenario,
type WorldSetupPreviewResponse,
type WorldSetupRequest,
} from '@agentborne/shared';
} from '@hexzero/shared';
import {
applyCommunication,
applyDiplomacy,
Expand All @@ -75,7 +75,7 @@ import {
expireAllianceProposals,
toWorldState,
type WorldState,
} from '@agentborne/world-engine';
} from '@hexzero/world-engine';
import {
createExperimentExport,
createExperimentPreview,
Expand Down
2 changes: 1 addition & 1 deletion apps/world-lab/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
allowedDevOrigins: ['127.0.0.1'],
transpilePackages: ['@agentborne/shared', '@agentborne/world-engine'],
transpilePackages: ['@hexzero/shared', '@hexzero/world-engine'],
experimental: { useTypeScriptCli: false },
async rewrites() {
return [
Expand Down
6 changes: 3 additions & 3 deletions apps/world-lab/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@agentborne/world-lab",
"name": "@hexzero/world-lab",
"version": "0.0.0",
"private": true,
"type": "module",
Expand All @@ -12,8 +12,8 @@
"typecheck": "next typegen && tsc --noEmit"
},
"dependencies": {
"@agentborne/shared": "workspace:*",
"@agentborne/world-engine": "workspace:*",
"@hexzero/shared": "workspace:*",
"@hexzero/world-engine": "workspace:*",
"h3-js": "4.5.0",
"maplibre-gl": "6.3.0",
"next": "16.3.1",
Expand Down
4 changes: 2 additions & 2 deletions apps/world-lab/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import 'maplibre-gl/dist/maplibre-gl.css';
import './styles.css';

export const metadata: Metadata = {
title: 'World Lab',
description: 'Developer interface for observing the Agentborne world.',
title: 'Hex Zero — World Lab',
description: 'Developer interface for observing the Hex Zero world.',
};

export default function RootLayout({
Expand Down
2 changes: 1 addition & 1 deletion apps/world-lab/src/components/model-options.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import type { CompatibleModel } from '@agentborne/shared';
import type { CompatibleModel } from '@hexzero/shared';
import { buildModelOptions, formatModelOption } from './model-options';

const model = (id: string, name: string, price = '0'): CompatibleModel => ({
Expand Down
2 changes: 1 addition & 1 deletion apps/world-lab/src/components/model-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CompatibleModel } from '@agentborne/shared';
import type { CompatibleModel } from '@hexzero/shared';

export interface ModelOption {
value: string;
Expand Down
2 changes: 1 addition & 1 deletion apps/world-lab/src/components/personality-presets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { personalitySchema } from '@agentborne/shared';
import { personalitySchema } from '@hexzero/shared';

export const PERSONALITY_PRESET_IDS = [
'aggressive-infector',
Expand Down
2 changes: 1 addition & 1 deletion apps/world-lab/src/components/ui-color.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import type { AgentId, SimulationSnapshot } from '@agentborne/shared';
import type { AgentId, SimulationSnapshot } from '@hexzero/shared';
import { neutralAgentColor, resolveAgentColor } from './ui-color';

const agentId = '11111111-1111-4111-8111-111111111111' as AgentId;
Expand Down
Loading
Loading