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
3 changes: 2 additions & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ ak sync # apply it
| opencode: `status` says `opencode.json is not plain JSON` | opencode legally allows JSONC comments; ak refuses to rewrite a file it can't parse rather than normalize (and silently drop) your comments | hand-merge the ak entries (`mcp`, `skills.paths`, `permission`) per `docs/adr/0017-opencode-host.md`, or remove the comments and run `ak sync` |
| opencode: an agent/skill/plugin file you created yourself keeps ak's version away | deploys are no-clobber: a file without ak's generated marker at the destination is treated as user-owned and preserved (`status` reports it as `foreign`) | rename yours (or delete it and `ak sync` to get ak's managed copy) |
| opencode: `status` says `no ruflo catalog source` | the agent/skill catalog resolves override → `$RUFLO_REPO` → claude marketplace clone → `@claude-flow/cli` (direct, then nested under ruflo) — all missing | install ruflo (`ak setup` does), or point `providers.opencodeCatalogDir` / `$RUFLO_REPO` at a ruflo checkout |
| `ruflo memory store` says OK but reads return nothing | Absolute-DB-path pin missing, or WAL not checkpointed, or the WASM fallback above | `ak setup` in the project re-pins + verifies a real write lands on disk |
| `ruflo memory store` says OK but reads return nothing | Absolute-DB-path pin missing, or an older check looked only at `.swarm/memory.db` while the native bridge selected `.swarm/agentdb-memory.db` | Run `ak x verify memory` for an isolated store/retrieve/on-disk/purge proof; `ak setup` re-pins and verifies the runtime-selected store |
| `status` shows a `codex-plugins` warning such as unknown field `_note` | An enabled plugin's newest cached hook file does not match Codex's `description` + `hooks` top-level schema | Open Codex `/plugins`, refresh or disable the named plugin, then start a new session. `ak sync` deliberately does not rewrite Codex-owned cache |
| `status` shows a `memory-pin` warning | `CLAUDE_FLOW_DB_PATH` is pinned to a dead or foreign path, so every memory op targets the wrong DB ("Database not initialized" beside a healthy in-repo DB). The pin may be deliberate, so `sync` never touches it | repoint (or remove) the pin in `.claude/settings.local.json` `env` |
| Deprecated `ak dual run` refuses to start ("ruflo's memory runtime lacks a native better-sqlite3 binding AND … active native WAL") | Pre-flight guard: the legacy orchestrator's native WAL writer and the WASM `ruflo memory store` would share one DB and corrupt it. It refuses **before** spawning a worker rather than crashing mid-run | Prefer `ak run` for new work. If an existing dual-run workflow must continue, `ak sync` builds the native binding, then retry it. |
| `ak provider` or `ak x provider` prints a deprecation warning | Alpha namespace correction: execution-host lifecycle and selection now belong to `host`; inference providers and bindings remain separate concepts | Use `ak host` (or `ak x host` for plumbing). The provider aliases will be removed before stable |
Expand Down
24 changes: 24 additions & 0 deletions docs/adr/0016-capability-driven-integration-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

- **Status:** Accepted
- **Date:** 2026-07-28
- **Updated:** 2026-07-30
- **Update note:** Added read-only Codex plugin-hook compatibility facts and
runtime-selected Ruflo project-memory store proofs.
- **Deciders:** agentic-kit maintainers
- **Related:** [ADR-0001](0001-one-routing-policy-many-projections.md),
[ADR-0003](0003-auto-seed-dual-host-provenance.md),
Expand Down Expand Up @@ -191,6 +194,24 @@ installation remains detectable and usable but unmanaged. This extends PR #67 an
no-clobber behavior across JSON, TOML, environment projections, and CLI-managed surfaces without
weakening it.

#### Externally-owned plugin cache and runtime-selected memory

Codex plugin configuration and `~/.codex/plugins/cache` are externally owned. Agentic-kit reads
every explicitly enabled plugin's newest cached manifest, follows its declared hook paths (or the
default `hooks/hooks.json`), and validates the Codex hook-file contract. It does not refresh,
rewrite, delete, or adopt any plugin cache entry. An invalid newest cached bundle is a diagnostic fact
with native remediation: open Codex `/plugins` to refresh or disable the plugin, then start a new
session. The row has no `sync` fix.

Project memory is also detected at fact level rather than inferred from package presence or a
single historical filename. Current native Ruflo bridges can preserve a compatibility/sql.js (or
encrypted) `.swarm/memory.db` while writing native plaintext rows to the sibling
`.swarm/agentdb-memory.db`. When the native sibling exists it is the active writer; the
compatibility store may coexist without representing drift. Read-only status identifies the active
writer and counts observable entries. Setup and `ak x verify memory` prove persistence by storing
a disposable row, locating it in the runtime-selected store, retrieving it through the real CLI,
and removing it. File or package presence alone is never reported as a persistence proof.

### 5. Normalize field-level facts before rendering conclusions

Detection and observation return facts, not pre-rendered status rows:
Expand Down Expand Up @@ -409,6 +430,8 @@ not write real home/global configuration.
- It does not claim provider provenance from host evidence alone.
- It does not add dashboard writes or controls.
- It does not silently adopt or overwrite externally managed configuration.
- It does not mutate Codex's plugin cache; plugin refresh and disable remain Codex-native actions.
- It does not collapse Ruflo's compatibility and native project-memory stores into one database.
- It does not implement or make unverified Ollama execution, usage-pricing, catalogue-metadata, or
transcript-fidelity claims; those remain gated independently by ADR-0011.

Expand Down Expand Up @@ -440,6 +463,7 @@ but less truthful.
| npm-managed vs external ownership is truthful | Section 4 |
| API keys are never persisted | Section 7 and serialization tests |
| Dry-run/idempotence/undo/no-clobber are shared and tested | Sections 3 and 4; conformance suite |
| External plugin hooks and runtime-selected memory are truthful | Section 4; read-only plugin diagnostics and isolated memory round-trip |
| Issue #59 can consume the abstraction without being subsumed | Sections 5 and 8 |
| Documentation uses one vocabulary | Section 9 |

Expand Down
24 changes: 12 additions & 12 deletions src/commands/setup.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { installedVersion } from '../lib/versions.mjs';
import * as rb from '../lib/ruvnet-brain.mjs';
import * as adb from '../lib/agentdb.mjs';
import { readJson, writeJsonWithBackup } from '../lib/settings.mjs';
import { checkpoint, withDb } from '../lib/sqlite.mjs';
import { withDb } from '../lib/sqlite.mjs';
import { findMemoryEntry } from '../lib/project-memory.mjs';
import * as paths from '../lib/paths.mjs';
import { ok, warn, fail, info, heading, bold, dim } from '../lib/output.mjs';

Expand Down Expand Up @@ -271,20 +272,19 @@ export async function run_project({ flags, cfg }) {
ok('claudeFlow.daemon.autoStart → false (explicit start only)');
}

// 7. WAL checkpoint + write-verification (store → on-disk row, then clean up)
checkpoint(dbPath);
const probeKey = `_setup/verify-${process.pid}`;
// 7. Write-verification (store → actual on-disk row, then clean up). Native
// bridges may select agentdb-memory.db beside the pinned compatibility DB.
const probeKey = `_setup/verify-${process.pid}-${Date.now()}`;
const stored = (await runCmd('ruflo', ['memory', 'store', '-k', probeKey, '--value', 'setup-verify', '-n', '_setup'], { cwd: root, env })).code === 0;
// Bound parameters, not string interpolation — probeKey is pid-derived and
// safe today, but interpolated SQL is a habit this codebase doesn't keep.
const onDisk = stored && withDb(dbPath,
(db) => db.prepare('SELECT COUNT(*) AS n FROM memory_entries WHERE key = ?').get(probeKey)?.n) === 1;
if (onDisk) {
withDb(dbPath, (db) => {
db.prepare('DELETE FROM memory_entries WHERE key = ?').run(probeKey);
const landed = stored ? findMemoryEntry(root, '_setup', probeKey) : null;
if (landed) {
// Bound parameters, not interpolation. Delete only this disposable probe
// from the store that actually received it.
withDb(landed.file, (db) => {
db.prepare('DELETE FROM memory_entries WHERE namespace = ? AND key = ?').run('_setup', probeKey);
db.exec('PRAGMA wal_checkpoint(TRUNCATE);');
}, null, { readonly: false });
ok('memory write VERIFIED (store → on-disk row confirmed)');
ok(`memory write VERIFIED (store → ${path.basename(landed.file)} row confirmed)`);
} else {
fail('memory write verification FAILED — run: ak status / ruflo doctor -c memory');
}
Expand Down
42 changes: 42 additions & 0 deletions src/commands/status.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { policyToAgentOverrides, routingSummary, divergedRoutes } from '../lib/r
import { qeCourtShipped, readQeCourtConfig, validateCourtConfig } from '../lib/qeCourt.mjs';
import { drift as ruvectorDrift } from '../lib/ruvector.mjs';
import { statuslineDrift } from '../lib/codex-statusline.mjs';
import { inspectCodexPlugins } from '../lib/codex-plugins.mjs';
import { projectMemoryStatus } from '../lib/project-memory.mjs';

export const options = {
json: { type: 'boolean', default: false },
Expand Down Expand Up @@ -194,6 +196,28 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) {
}
} catch { /* pin check is best-effort — never blocks status */ }

// Project memory may legitimately have two stores: the compatibility/sql.js
// memory.db and the native bridge's plaintext agentdb-memory.db sibling.
// Presence is a quick signal only; `ak x verify memory` performs the write
// round-trip proof.
try {
const memory = projectMemoryStatus(cwd);
if (!memory.active) {
rows.push(row('memory', 'info', 'no project memory store yet (run setup here to initialize)'));
} else if (!memory.active.readable) {
rows.push(row('memory', 'warn',
`active ${memory.active.kind} store is unreadable (${memory.active.file}) — run: ak x verify memory`));
} else {
const sibling = memory.secondary
? `; ${memory.secondary.kind} compatibility store also present`
: '';
rows.push(row('memory', 'ok',
`${memory.active.kind} active writer: ${memory.active.entries} active entr${memory.active.entries === 1 ? 'y' : 'ies'}${sibling}`));
}
} catch (e) {
rows.push(row('memory', 'warn', `project memory check unavailable: ${e.message}`));
}

// npx (stale ruflo-family cache envs — `npx --prefer-offline` fallbacks in the
// statusline/hooks execute these verbatim, keeping retired defects alive)
try {
Expand Down Expand Up @@ -325,6 +349,24 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) {
}
}

// Codex owns plugin installation and refresh. Inspect every explicitly
// enabled cached plugin, but never attach a sync fix: the supported repair
// surface is Codex's /plugins UI followed by a fresh session.
try {
const plugins = inspectCodexPlugins();
if (plugins.enabled.length && plugins.issues.length) {
rows.push(row('codex-plugins', 'warn',
`${plugins.issues.length} Codex plugin compatibility issue(s): ${plugins.issues[0]}; `
+ 'open Codex /plugins to refresh or disable it, then start a new session'));
} else if (plugins.enabled.length) {
const versions = plugins.plugins.map((plugin) => `${plugin.ref} (${plugin.version})`).join(', ');
rows.push(row('codex-plugins', 'ok',
`${plugins.enabled.length} enabled Codex plugin(s); newest cached hook configs compatible (${versions})`));
}
} catch (e) {
rows.push(row('codex-plugins', 'warn', `Codex plugin check unavailable: ${e.message}`));
}

// opencode host wiring — the third host's counterpart of the codex-mcp rows:
// opencode.json (mcp + skills.paths + permissions), the plugins/ lifecycle
// bridge, the converted agent set, and the platform skill. Only surfaces when
Expand Down
64 changes: 60 additions & 4 deletions src/commands/x/verify.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// x verify [learning|security|aqe|all] — the deep proofs (slow, spawn real
// x verify [learning|memory|security|aqe|all] — the deep proofs (slow, spawn real
// CLIs). Ports of ruflo-learning-verify, ruflo-security-verify's defend
// exercise, and ruflo-verify-aqe's live checks.
import fs from 'node:fs';
Expand All @@ -7,7 +7,8 @@ import path from 'node:path';
import { run as runCmd, have } from '../../lib/exec.mjs';
import { aidefencePresent, securityPresent } from '../../lib/natives.mjs';
import { scanRvf } from '../../lib/rvf.mjs';
import { projectAqeDir } from '../../lib/paths.mjs';
import { projectAqeDir, projectMemoryDb } from '../../lib/paths.mjs';
import { findMemoryEntry } from '../../lib/project-memory.mjs';
import { loadKitConfig } from '../../lib/config.mjs';
import { HOSTS, collectIntegrationFacts, aqeRouterFile } from '../../lib/providers.mjs';
import { readJson } from '../../lib/settings.mjs';
Expand All @@ -25,6 +26,7 @@ Usage: ak x verify [suite]

Suites:
learning train a cycle in a temp dir; assert patterns persist
memory store/retrieve/purge in a temp dir; confirm the actual DB writer
security packages load; defend flags injection / passes clean
aqe RVF store healthy; aqe status has no FsyncFailed
providers kit config matches installed CLIs; ruflo/aqe see the wiring
Expand Down Expand Up @@ -54,6 +56,60 @@ async function verifyLearning() {
}
}

async function verifyMemory() {
heading('memory — store, retrieve, inspect the actual writer, and purge in an isolated dir');
if (!(await have('ruflo'))) { fail('ruflo CLI not installed — cannot prove project memory'); return false; }
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'agentic-kit-memory-'));
const namespace = `agentic-kit-verify-${process.pid}-${Date.now()}`;
const key = 'roundtrip';
const value = `memory-proof-${process.pid}-${Date.now()}`;
const env = {
CLAUDE_FLOW_DB_PATH: projectMemoryDb(tmp),
RUFLO_DAEMON_AUTOSTART: '0',
};
let stored = false;
let purged = false;
try {
const init = await runCmd('ruflo', ['memory', 'init'], { cwd: tmp, env, timeout: 120_000 });
if (init.code !== 0) { fail('ruflo memory init failed'); return false; }
const put = await runCmd('ruflo',
['memory', 'store', '-k', key, '--value', value, '-n', namespace],
{ cwd: tmp, env, timeout: 120_000 });
if (put.code !== 0) { fail(`ruflo memory store failed: ${(put.stderr || '').slice(0, 160)}`); return false; }
stored = true;

const get = await runCmd('ruflo',
['memory', 'retrieve', '-k', key, '-n', namespace, '--value-only'],
{ cwd: tmp, env, timeout: 120_000 });
if (get.code !== 0 || !get.stdout.includes(value)) {
fail('ruflo memory retrieve did not return the exact stored value');
return false;
}
ok('CLI store → retrieve returned the exact value');

const landed = findMemoryEntry(tmp, namespace, key);
if (!landed) { fail('stored value was not observable in either supported project DB'); return false; }
ok(`on-disk row confirmed in ${path.basename(landed.file)} (${landed.kind})`);

const purge = await runCmd('ruflo',
['memory', 'purge', '--namespace', namespace, '--force'],
{ cwd: tmp, env, timeout: 120_000 });
purged = purge.code === 0 && !findMemoryEntry(tmp, namespace, key);
if (!purged) { fail('isolated namespace purge did not remove the proof row'); return false; }
ok('isolated proof namespace purged');
return true;
} catch (e) {
fail(`memory verify error: ${e.message}`);
return false;
} finally {
if (stored && !purged) {
await runCmd('ruflo', ['memory', 'purge', '--namespace', namespace, '--force'],
{ cwd: tmp, env, timeout: 120_000 });
}
fs.rmSync(tmp, { recursive: true, force: true });
}
}

async function verifySecurity() {
heading('security — packages load, defend flags injection / passes clean');
let good = true;
Expand Down Expand Up @@ -153,9 +209,9 @@ async function verifyHarvest() {

export async function run({ positionals }) {
const which = positionals[0] ?? 'all';
const suites = { learning: verifyLearning, security: verifySecurity, aqe: verifyAqe, providers: verifyProviders, harvest: verifyHarvest };
const suites = { learning: verifyLearning, memory: verifyMemory, security: verifySecurity, aqe: verifyAqe, providers: verifyProviders, harvest: verifyHarvest };
const selected = which === 'all' ? Object.entries(suites) : [[which, suites[which]]];
if (!selected.every(([, fn]) => fn)) { fail(`unknown suite: ${which} (learning|security|aqe|providers|harvest|all)`); return 2; }
if (!selected.every(([, fn]) => fn)) { fail(`unknown suite: ${which} (learning|memory|security|aqe|providers|harvest|all)`); return 2; }
let allGood = true;
for (const [, fn] of selected) allGood = (await fn()) && allGood;
console.log('');
Expand Down
Loading
Loading