Skip to content

Commit 003204a

Browse files
committed
feat: add host llm policy routing
1 parent 103dbaf commit 003204a

11 files changed

Lines changed: 160 additions & 17 deletions

File tree

docs/PARACOSM.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "Paracosm — AI Simulation Engine"
33
sidebar_position: 1
44
---
55

6-
Paracosm is an AI agent swarm simulation engine built on AgentOS. Define a scenario as JSON, run it with AI commanders that have different [HEXACO](/features/hexaco-personality) personality profiles, and watch their decisions diverge into measurably different outcomes from the same seed. The reference scenario ships as Mars Genesis: a thirty-colonist Mars colony running from 2035 to 2067 across six turns.
6+
Paracosm is an AI agent swarm simulation engine built on AgentOS. Define a scenario as JSON, run it with AI commanders that have different [HEXACO](/features/cognitive-memory) personality profiles, and watch their decisions diverge into measurably different outcomes from the same seed. The reference scenario ships as Mars Genesis: a thirty-colonist Mars colony running from 2035 to 2067 across six turns.
77

88
**[Live demo](https://paracosm.agentos.sh/sim)** · **[GitHub](https://github.com/framersai/paracosm)** · **[npm](https://www.npmjs.com/package/paracosm)** · **[API reference](/paracosm)** · **[Case study blog post](https://agentos.sh/blog/inside-mars-genesis-ai-colony-simulation)**
99

@@ -61,7 +61,7 @@ Two runs on the same seed produce identical deterministic stages. The LLM stages
6161

6262
## How HEXACO drives decisions
6363

64-
Paracosm uses the [HEXACO model](/features/hexaco-personality) (Ashton & Lee, 2007) across all six axes, with both poles producing concrete behavioral cues in the commander's decision-style block and the department analysis prompts:
64+
Paracosm uses the [HEXACO model](/features/cognitive-memory) (Ashton & Lee, 2007) across all six axes, with both poles producing concrete behavioral cues in the commander's decision-style block and the department analysis prompts:
6565

6666
- **Openness** — high: favor novel, untested approaches; low: trust proven protocols.
6767
- **Conscientiousness** — high: demand evidence and contingency plans; low: move fast, accept ambiguity.
@@ -100,7 +100,7 @@ Any domain works. Mars colonies, submarine habitats, space stations, medieval ki
100100
}
101101
```
102102

103-
`compileScenario()` turns JSON into a runnable `ScenarioPackage` by generating TypeScript hook functions via LLM calls. Compilation costs about $0.10 per scenario and caches to disk. See [`compileScenario`](/paracosm/functions/compileScenario) for the full hook contract.
103+
`compileScenario()` turns JSON into a runnable `ScenarioPackage` by generating TypeScript hook functions via LLM calls. Compilation costs about $0.10 per scenario and caches to disk. See [`compileScenario`](/paracosm/engine/compiler/functions/compileScenario) for the full hook contract.
104104

105105
## Cost safety
106106

@@ -128,8 +128,8 @@ Full type reference is auto-generated from source at [/paracosm](/paracosm). The
128128
- [`LeaderConfig`](/paracosm/interfaces/LeaderConfig) — commander identity + HEXACO profile
129129
- [`HexacoProfile`](/paracosm/interfaces/HexacoProfile) — six-axis personality vector
130130
- [`SimulationKernel`](/paracosm/classes/SimulationKernel) — deterministic state machine
131-
- [`runSimulation`](/paracosm/functions/runSimulation) — single-leader turn loop
132-
- [`runBatch`](/paracosm/functions/runBatch) — parallel multi-scenario runner
131+
- [`runSimulation`](/paracosm/runtime/functions/runSimulation) — single-leader turn loop
132+
- [`runBatch`](/paracosm/runtime/functions/runBatch) — parallel multi-scenario runner
133133

134134
## HTTP + SSE server
135135

@@ -151,7 +151,7 @@ The dashboard server exposes a small HTTP API for driving sims from any client:
151151
## Related
152152

153153
- [Emergent Capabilities](/features/emergent-capabilities) — the forge + judge machinery underlying `forge_tool`
154-
- [HEXACO Personality](/features/hexaco-personality) — trait model, mutation system, persona overlays
154+
- [HEXACO Personality](/features/cognitive-memory) — trait model, mutation system, persona overlays
155155
- [Cognitive Memory Guide](/features/cognitive-memory-guide) — the memory pipeline colonists use as chat agents
156156
- [Inside Mars Genesis (blog)](https://agentos.sh/blog/inside-mars-genesis-ai-colony-simulation) — full case study
157157
- [Emergent Tools and HEXACO Leaders (blog)](https://agentos.sh/blog/emergent-tools-hexaco-leaders) — two-leader-one-seed comparison

docs/architecture/EMERGENT_CAPABILITIES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,4 +529,4 @@ await exportToolAsSkillPack(forgedTool, './skills/slugify');
529529
- [Recursive Self-Building](/features/recursive-self-building) -- recursive tool creation and agent spawning
530530
- [Guardrails](/features/guardrails) -- safety mechanisms that constrain emergent behavior
531531
- [Agency API](/features/agency-api) -- multi-agent coordination strategies
532-
- **API Reference:** [`EmergentCapabilityEngine`](/api/classes/EmergentCapabilityEngine) | [`EmergentJudge`](/api/classes/EmergentJudge) | [`EmergentToolRegistry`](/api/classes/EmergentToolRegistry) | [`ForgeToolMetaTool`](/api/classes/ForgeToolMetaTool) | [`ComposableToolBuilder`](/api/classes/ComposableToolBuilder) | [`CodeSandbox`](/api/classes/CodeSandbox) | [`AdaptPersonalityTool`](/api/classes/AdaptPersonalityTool) | [`ManageSkillsTool`](/api/classes/ManageSkillsTool) | [`SelfEvaluateTool`](/api/classes/SelfEvaluateTool) | [`CreateWorkflowTool`](/api/classes/CreateWorkflowTool) | [`SkillExporter`](/api/classes/SkillExporter)
532+
- **API Reference:** [`EmergentCapabilityEngine`](/api/classes/EmergentCapabilityEngine) | [`EmergentJudge`](/api/classes/EmergentJudge) | [`EmergentToolRegistry`](/api/classes/EmergentToolRegistry) | [`ForgeToolMetaTool`](/api/classes/ForgeToolMetaTool) | [`ComposableToolBuilder`](/api/classes/ComposableToolBuilder) | [`CodeSandbox`](/api/classes/CodeSandbox) | [`AdaptPersonalityTool`](/api/classes/AdaptPersonalityTool) | [`ManageSkillsTool`](/api/classes/ManageSkillsTool) | [`SelfEvaluateTool`](/api/classes/SelfEvaluateTool) | [`CreateWorkflowTool`](/api/classes/CreateWorkflowTool) | [`exportToolAsSkill`](/api/functions/exportToolAsSkill)

docs/publication-manifest.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ const publicationManifest = [
215215

216216
agentosDoc('IMAGE_GENERATION.md', 'features/image-generation.md', 'Image Generation', 'Media Generation', 1),
217217
agentosDoc('IMAGE_EDITING.md', 'features/image-editing.md', 'Image Editing (Img2Img, Inpainting, Upscaling)', 'Media Generation', 2),
218+
agentosDoc('CHARACTER_CONSISTENCY.md', 'features/character-consistency.md', 'Character Consistency', 'Media Generation', 2.5),
219+
agentosDoc('STYLE_TRANSFER.md', 'features/style-transfer.md', 'Style Transfer', 'Media Generation', 2.6),
218220
agentosDoc('VISION_PIPELINE.md', 'features/vision-pipeline.md', 'Vision Pipeline (OCR & Image Understanding)', 'Media Generation', 3),
219221
staticDoc('features/audio-generation.md', 'features/audio-generation.md', 'Audio Generation', 'Media Generation', 4),
220222
staticDoc('features/provider-preferences.md', 'features/provider-preferences.md', 'Provider Preferences', 'Media Generation', 5),
@@ -228,6 +230,7 @@ const publicationManifest = [
228230
siteDoc('features/llm-output-validation.md', 'features/llm-output-validation.md', 'LLM Output Validation', 'AI & LLM', 1.5),
229231
agentosDoc('EVALUATION.md', 'features/evaluation-guide.md', 'Evaluation Guide', 'AI & LLM', 2),
230232
agentosDoc('COST_OPTIMIZATION.md', 'features/cost-optimization.md', 'Cost Optimization', 'AI & LLM', 3),
233+
agentosDoc('UNCENSORED_CONTENT.md', 'features/uncensored-content.md', 'Uncensored Content & Policy-Tier Routing', 'AI & LLM', 3.5),
231234
agentosDoc('EVALUATION_FRAMEWORK.md', 'features/evaluation-framework.md', 'Evaluation Framework', 'AI & LLM', 4, {
232235
sidebar: false,
233236
}),

src/api/agent.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
type ToolCallHookInfo,
2222
} from './generateText.js';
2323
import { streamText, type StreamTextResult } from './streamText.js';
24+
import type { HostLLMPolicy } from './runtime/hostPolicy.js';
2425
import type { IModelRouter } from '../core/llm/routing/IModelRouter.js';
2526
import type { SkillEntry } from '../skills/types.js';
2627
import type {
@@ -71,6 +72,8 @@ export interface AgentOptions extends BaseAgentConfig {
7172
onFallback?: (error: Error, fallbackProvider: string) => void;
7273
/** Model router for intelligent provider selection per-call. */
7374
router?: IModelRouter;
75+
/** Host-level routing hints forwarded to the high-level generation helpers. */
76+
hostPolicy?: HostLLMPolicy;
7477
/**
7578
* Routing hints passed to the model router's `selectModel()` call.
7679
*
@@ -391,6 +394,7 @@ export function agent(opts: AgentOptions): Agent {
391394
fallbackProviders: opts.fallbackProviders,
392395
onFallback: opts.onFallback,
393396
router: opts.router,
397+
hostPolicy: opts.hostPolicy,
394398
routerParams: opts.routerParams,
395399
onBeforeGeneration: opts.onBeforeGeneration,
396400
onAfterGeneration: opts.onAfterGeneration,

src/api/generateText.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
import { randomUUID } from 'node:crypto';
1515
import { resolveModelOption, resolveProvider, createProviderManager } from './model.js';
1616
import { attachUsageAttributes, toTurnMetricUsage } from './observability.js';
17+
import {
18+
hostPolicyToRouteParams,
19+
mergeRequiredCapabilities,
20+
type HostLLMPolicy,
21+
} from './runtime/hostPolicy.js';
1722
import { adaptTools, type AdaptableToolInput } from './runtime/toolAdapter.js';
1823
import type { AgentOSUsageLedgerOptions } from './runtime/usageLedger.js';
1924
import { resolveDynamicToolCalls } from './runtime/dynamicToolCalling.js';
@@ -31,6 +36,7 @@ import type {
3136

3237
// Re-export multimodal types for downstream consumers
3338
export type { MessageContent, MessageContentPart };
39+
export type { HostLLMPolicy } from './runtime/hostPolicy.js';
3440

3541
async function recordAgentOSUsageLazy(
3642
input: Parameters<typeof import('./runtime/usageLedger.js')['recordAgentOSUsage']>[0]
@@ -303,6 +309,11 @@ export interface GenerateTextOptions {
303309
* from system prompt and tool names when not provided.
304310
*/
305311
routerParams?: Partial<ModelRouteParams>;
312+
/**
313+
* Host-level routing hints that can be forwarded into the model router
314+
* without requiring callers to construct raw router params directly.
315+
*/
316+
hostPolicy?: HostLLMPolicy;
306317
/**
307318
* Called before each LLM generation step. Can inject memory context
308319
* into messages, sanitize input via guardrails, or modify the prompt.
@@ -726,15 +737,28 @@ export async function generateText(opts: GenerateTextOptions): Promise<GenerateT
726737
.map((t: any) => t.name ?? t.function?.name)
727738
.filter(Boolean) as string[]
728739
: [];
740+
const hostPolicyRouteParams = hostPolicyToRouteParams(opts.hostPolicy);
741+
const requiredCapabilities = mergeRequiredCapabilities(
742+
hostPolicyRouteParams.requiredCapabilities,
743+
opts.routerParams?.requiredCapabilities,
744+
toolNames.length > 0 ? ['function_calling'] : undefined,
745+
);
729746
const routeParams: ModelRouteParams = {
730747
taskHint:
731748
opts.routerParams?.taskHint ?? (typeof opts.system === 'string' ? opts.system : undefined) ?? opts.prompt ?? '',
732-
requiredCapabilities:
733-
opts.routerParams?.requiredCapabilities ??
734-
(toolNames.length > 0 ? ['function_calling'] : undefined),
735-
optimizationPreference:
736-
opts.routerParams?.optimizationPreference ?? 'balanced',
749+
...hostPolicyRouteParams,
737750
...opts.routerParams,
751+
optimizationPreference:
752+
opts.routerParams?.optimizationPreference
753+
?? hostPolicyRouteParams.optimizationPreference
754+
?? 'balanced',
755+
requiredCapabilities,
756+
preferredProviderIds:
757+
opts.routerParams?.preferredProviderIds
758+
?? hostPolicyRouteParams.preferredProviderIds,
759+
policyTier:
760+
opts.routerParams?.policyTier
761+
?? hostPolicyRouteParams.policyTier,
738762
};
739763
const routeResult = await opts.router.selectModel(
740764
routeParams,

src/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ export {
2323
type GenerateTextOptions,
2424
type GenerateTextResult,
2525
type FallbackProviderEntry,
26+
type HostLLMPolicy,
2627
type Message,
2728
type ToolCallRecord,
2829
type TokenUsage,
2930
} from './generateText.js';
31+
export { normalizeHostLLMPolicy } from './runtime/hostPolicy.js';
3032
export { streamText } from './streamText.js';
3133
export { generateObject } from './generateObject.js';
3234
export { streamObject } from './streamObject.js';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { normalizeHostLLMPolicy } from '../hostPolicy.js';
4+
5+
describe('HostLLMPolicy', () => {
6+
it('normalizes optimization, capability, and fallback hints for host apps', () => {
7+
const policy = normalizeHostLLMPolicy({
8+
optimizationPreference: 'cost',
9+
requiredCapabilities: ['json_mode', 'tool_use'],
10+
allowedProviders: ['openai', 'anthropic'],
11+
fallbackProviders: [{ provider: 'openai', model: 'gpt-4.1-mini' }],
12+
cacheDiscipline: 'stable_prefix',
13+
});
14+
15+
expect(policy.optimizationPreference).toBe('cost');
16+
expect(policy.requiredCapabilities).toEqual(['json_mode', 'tool_use']);
17+
expect(policy.allowedProviders).toEqual(['openai', 'anthropic']);
18+
expect(policy.fallbackProviders).toEqual([{ provider: 'openai', model: 'gpt-4.1-mini' }]);
19+
expect(policy.policyTier).toBe('standard');
20+
expect(policy.cacheDiscipline).toBe('stable_prefix');
21+
});
22+
});

src/api/runtime/__tests__/modelRouter.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,31 @@ describe('ModelRouter integration', () => {
163163
);
164164
});
165165

166+
it('threads hostPolicy hints into router selection', async () => {
167+
const router = createMockRouter(null);
168+
169+
await generateText({
170+
prompt: 'hello',
171+
router,
172+
hostPolicy: {
173+
optimizationPreference: 'cost',
174+
requiredCapabilities: ['json_mode'],
175+
allowedProviders: ['anthropic'],
176+
policyTier: 'mature',
177+
},
178+
});
179+
180+
expect(router.selectModel).toHaveBeenCalledWith(
181+
expect.objectContaining({
182+
optimizationPreference: 'cost',
183+
requiredCapabilities: ['json_mode'],
184+
preferredProviderIds: ['anthropic'],
185+
policyTier: 'mature',
186+
}),
187+
undefined,
188+
);
189+
});
190+
166191
it('auto-extracts taskHint from system prompt when routerParams not provided', async () => {
167192
const router = createMockRouter(null);
168193

src/api/runtime/hostPolicy.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { ModelRouteParams } from '../../core/llm/routing/IModelRouter.js';
2+
3+
export interface HostLLMPolicy {
4+
optimizationPreference?: 'cost' | 'speed' | 'quality' | 'balanced';
5+
requiredCapabilities?: string[];
6+
allowedProviders?: string[];
7+
fallbackProviders?: Array<{ provider: string; model?: string }>;
8+
policyTier?: 'safe' | 'standard' | 'mature' | 'private-adult';
9+
cacheDiscipline?: 'none' | 'stable_prefix' | 'structured_blocks';
10+
}
11+
12+
export function normalizeHostLLMPolicy(input: HostLLMPolicy = {}): Required<HostLLMPolicy> {
13+
return {
14+
optimizationPreference: input.optimizationPreference ?? 'balanced',
15+
requiredCapabilities: input.requiredCapabilities ?? [],
16+
allowedProviders: input.allowedProviders ?? [],
17+
fallbackProviders: input.fallbackProviders ?? [],
18+
policyTier: input.policyTier ?? 'standard',
19+
cacheDiscipline: input.cacheDiscipline ?? 'none',
20+
};
21+
}
22+
23+
export function hostPolicyToRouteParams(hostPolicy?: HostLLMPolicy): Partial<ModelRouteParams> {
24+
if (!hostPolicy) return {};
25+
26+
const normalized = normalizeHostLLMPolicy(hostPolicy);
27+
return {
28+
optimizationPreference: normalized.optimizationPreference,
29+
requiredCapabilities:
30+
normalized.requiredCapabilities.length > 0 ? [...normalized.requiredCapabilities] : undefined,
31+
preferredProviderIds:
32+
normalized.allowedProviders.length > 0 ? [...normalized.allowedProviders] : undefined,
33+
policyTier: normalized.policyTier,
34+
};
35+
}
36+
37+
export function mergeRequiredCapabilities(
38+
...capabilitySets: Array<string[] | undefined>
39+
): string[] | undefined {
40+
const merged = capabilitySets
41+
.flatMap((capabilities) => capabilities ?? [])
42+
.filter((capability, index, allCapabilities) => {
43+
return capability.length > 0 && allCapabilities.indexOf(capability) === index;
44+
});
45+
46+
return merged.length > 0 ? merged : undefined;
47+
}

src/api/streamText.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import { randomUUID } from 'node:crypto';
1111
import { resolveModelOption, resolveProvider, createProviderManager } from './model.js';
1212
import { attachUsageAttributes, toTurnMetricUsage } from './observability.js';
13+
import { hostPolicyToRouteParams, mergeRequiredCapabilities } from './runtime/hostPolicy.js';
1314
import { adaptTools } from './runtime/toolAdapter.js';
1415
import {
1516
buildFallbackChain,
@@ -164,15 +165,28 @@ export function streamText(opts: GenerateTextOptions): StreamTextResult {
164165
.map((t: any) => t.name ?? t.function?.name)
165166
.filter(Boolean) as string[]
166167
: [];
168+
const hostPolicyRouteParams = hostPolicyToRouteParams(opts.hostPolicy);
169+
const requiredCapabilities = mergeRequiredCapabilities(
170+
hostPolicyRouteParams.requiredCapabilities,
171+
opts.routerParams?.requiredCapabilities,
172+
toolNames.length > 0 ? ['function_calling'] : undefined,
173+
);
167174
const routeParams: ModelRouteParams = {
168175
taskHint:
169176
opts.routerParams?.taskHint ?? (typeof opts.system === 'string' ? opts.system : undefined) ?? opts.prompt ?? '',
170-
requiredCapabilities:
171-
opts.routerParams?.requiredCapabilities ??
172-
(toolNames.length > 0 ? ['function_calling'] : undefined),
173-
optimizationPreference:
174-
opts.routerParams?.optimizationPreference ?? 'balanced',
177+
...hostPolicyRouteParams,
175178
...opts.routerParams,
179+
optimizationPreference:
180+
opts.routerParams?.optimizationPreference
181+
?? hostPolicyRouteParams.optimizationPreference
182+
?? 'balanced',
183+
requiredCapabilities,
184+
preferredProviderIds:
185+
opts.routerParams?.preferredProviderIds
186+
?? hostPolicyRouteParams.preferredProviderIds,
187+
policyTier:
188+
opts.routerParams?.policyTier
189+
?? hostPolicyRouteParams.policyTier,
176190
};
177191
const routeResult = await opts.router.selectModel(
178192
routeParams,

0 commit comments

Comments
 (0)