mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-19 04:21:24 +02:00
Three Claude-Code-inspired primitives for runPlannerLoop, derived from the
reverse-engineering reports in docs/reports/:
1. **Policy gate** (@mana/tool-registry) — evaluatePolicy() gates every tool
dispatch: denies admin-scope, denies destructive tools not in the user's
opt-in list, rate-limits per tool (30/60s default), flags prompt-injection
markers in freetext without blocking. Wired into mana-mcp with a
per-user rolling invocation log and POLICY_MODE env (off|log-only|enforce,
default log-only). mana-ai uses detectInjectionMarker only — tool dispatch
there is plan-only, so rate-limit/destructive checks don't apply yet.
2. **Reminder channel** (packages/shared-ai/src/planner/loop.ts) — new
reminderChannel callback in PlannerLoopInput. Called once per round with
LoopState snapshot (round, toolCallCount, usage, lastCall); returned
strings wrap in <reminder> tags and inject as transient system messages
into THIS LLM request only. Never pushed to messages[] — the Claude-Code
<system-reminder> pattern that keeps the KV-cache prefix stable.
3. **Parallel reads** (loop.ts) — isParallelSafe predicate enables
Promise.all dispatch when every tool_call in a round is parallel-safe,
in batches of PARALLEL_TOOL_BATCH_SIZE=10. Any non-safe call downgrades
the whole round to sequential. messages[] always appends in source
order, never completion order, so the debug log stays linear.
Default-off (undefined predicate) preserves pre-M1 behaviour.
Tests: 21 new in tool-registry (policy), 9 new in shared-ai (5 parallel,
4 reminder). All 74 green, type-check clean across 4 packages.
Design/plan: docs/plans/agent-loop-improvements-m1.md
Reports: docs/reports/claude-code-architecture.md,
docs/reports/mana-agent-improvements-from-claude-code.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
/**
|
|
* Service configuration. All values come from env. Defaults match local
|
|
* dev (`pnpm setup:env` writes the same values into .env files).
|
|
*/
|
|
|
|
export interface Config {
|
|
port: number;
|
|
authUrl: string;
|
|
jwtAudience: string;
|
|
manaSyncUrl: string;
|
|
corsOrigins: string[];
|
|
/**
|
|
* Policy enforcement mode:
|
|
* 'off' — no policy evaluation (legacy behaviour).
|
|
* 'log-only' — evaluate, record metrics, but never deny a call.
|
|
* Used during the M1 soak period (see docs/plans/
|
|
* agent-loop-improvements-m1.md §Rollout).
|
|
* 'enforce' — deny calls whose decision is allow:false.
|
|
*/
|
|
policyMode: 'off' | 'log-only' | 'enforce';
|
|
}
|
|
|
|
function intEnv(name: string, fallback: number): number {
|
|
const raw = process.env[name];
|
|
if (!raw) return fallback;
|
|
const n = Number(raw);
|
|
if (!Number.isInteger(n) || n <= 0) {
|
|
throw new Error(`${name} must be a positive integer, got "${raw}"`);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
function parsePolicyMode(raw: string | undefined): Config['policyMode'] {
|
|
const v = (raw ?? 'log-only').toLowerCase();
|
|
if (v === 'off' || v === 'log-only' || v === 'enforce') return v;
|
|
throw new Error(`POLICY_MODE must be off|log-only|enforce, got "${raw}"`);
|
|
}
|
|
|
|
export function loadConfig(): Config {
|
|
return {
|
|
port: intEnv('PORT', 3069),
|
|
authUrl: process.env.MANA_AUTH_URL ?? 'http://localhost:3001',
|
|
jwtAudience: process.env.JWT_AUDIENCE ?? 'mana',
|
|
manaSyncUrl: process.env.MANA_SYNC_URL ?? 'http://localhost:3050',
|
|
corsOrigins: (process.env.CORS_ORIGINS ?? 'http://localhost:5173')
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean),
|
|
policyMode: parsePolicyMode(process.env.POLICY_MODE),
|
|
};
|
|
}
|