akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
69 lines (68 loc) • 3.34 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
import { IMPROVE_AUTONOMY_CONFIG_KEY, isImproveAutonomyEnabled, } from "../../core/config/experimental.js";
/** The lanes `experimental.improveAutonomy` gates. */
export const AUTONOMY_LANES = ["memoryInference", "triagePromote", "memoryCleanup"];
const LANE_REASONS = {
memoryInference: "writes derived memory children and rewrites parent frontmatter",
triagePromote: "auto-accepts queued proposals into the stash (downgraded to queue)",
memoryCleanup: "rewrites belief-state frontmatter and moves files into the cleanup archive",
};
/**
* The lanes that bypass the strategy config and ask the gate at their own call
* site. They have no `processes.<name>.enabled` flag to downgrade, so
* {@link applyAutonomyGate} cannot see them — anything reporting the full gated
* set has to add these.
*/
export const DIRECT_AUTONOMY_LANES = ["memoryCleanup"];
function gatedLane(lane) {
return { lane, configKey: IMPROVE_AUTONOMY_CONFIG_KEY, reason: LANE_REASONS[lane] };
}
/**
* Describe lanes for reporting — the warning line, the `improve_skipped` event,
* and `akm task doctor` all render the same {@link GatedLane} shape, so the
* lane name, config key, and reason cannot drift between the three surfaces.
*/
export function describeGatedLanes(lanes) {
return lanes.map(gatedLane);
}
/** Configured capabilities that tasks doctor reports behind the autonomy gate. */
export function configuredDirectAutonomyLanes() {
return [...DIRECT_AUTONOMY_LANES];
}
/**
* True when a lane may mutate. Used by the lane that bypasses the strategy
* config; the other three are handled by {@link applyAutonomyGate}.
*/
export function isAutonomyLaneAllowed(_lane, config) {
// Every lane shares one opt-in today. The parameter is kept so a call site
// names the lane it is asking about — that name is what reaches the operator
// in the skip event — and so a future per-lane split does not have to revisit
// every caller.
return isImproveAutonomyEnabled(config);
}
/**
* Downgrade a strategy config to review-first unless autonomy is opted into.
*
* Returns the config to actually run plus every downgrade made. With autonomy on
* the input is returned untouched and `gated` is empty.
*/
export function applyAutonomyGate(strategy, config) {
if (isImproveAutonomyEnabled(config))
return { config: strategy, gated: [] };
const gated = [];
const processes = { ...(strategy.processes ?? {}) };
if (processes.memoryInference?.enabled === true) {
processes.memoryInference = { ...processes.memoryInference, enabled: false };
gated.push(gatedLane("memoryInference"));
}
// Triage stays ENABLED — queued proposals are still triaged, they just are not
// auto-accepted. Disabling it would remove review work the user asked for,
// which is the opposite of what a review-first default should do.
if (processes.triage?.applyMode === "promote") {
processes.triage = { ...processes.triage, applyMode: "queue" };
gated.push(gatedLane("triagePromote"));
}
return { config: { ...strategy, processes }, gated };
}