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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tangle-network/agent-knowledge",
"version": "3.2.0",
"version": "3.2.1",
"description": "Source-grounded, eval-gated knowledge growth primitives for agents.",
"homepage": "https://github.com/tangle-network/agent-knowledge#readme",
"repository": {
Expand Down
38 changes: 23 additions & 15 deletions src/kb-improvement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,25 +841,33 @@ async function improveKnowledgeBaseInRun(
if (state.status === 'promoted' && !promotedCandidate) {
throw new Error('promoted knowledge state has no promoted candidate')
}
await withKnowledgeMutation(options.root, () => undefined)
await ensureBaselineSnapshot(runDir, options.root, state.baseHash)

if (state.status === 'promoted') {
const promoted = promotedCandidate!
return await applyKnowledgeCandidateTarget(
{
root: options.root,
const promotedState = state
return withKnowledgeMutation(options.root, async () => {
const currentHash = await hashKnowledgeBase(options.root)
if (currentHash !== promoted.candidateHash) {
throw new Error(
`promoted knowledge base changed: expected ${promoted.candidateHash}, got ${currentHash}`,
)
}
const evidence = await assertCandidateEvidence(
runDir,
state,
candidateRef: candidateRefFor(runId, state, promoted),
leaseTtlMs: options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS,
assertRunOwned: lease.assertOwned,
now,
onState: options.onState,
},
'candidate',
)
candidateRefFor(runId, promotedState, promoted),
)
return {
runId,
state: promotedState,
candidate: promoted,
evaluation: evidence.evaluation,
promoted: true,
blocked: false,
}
})
}
await withKnowledgeMutation(options.root, () => undefined)
await ensureBaselineSnapshot(runDir, options.root, state.baseHash)

if (state.status === 'blocked') {
return { runId, state, promoted: false, blocked: true }
}
Expand Down
26 changes: 26 additions & 0 deletions tests/kb-improvement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,32 @@ describe('improveKnowledgeBase', () => {
})
})

it('does not reapply a promoted candidate when the improvement run is reopened', async () => {
await withEmptyRoot(async (root) => {
const options = {
root,
goal: 'Keep candidate generation separate from activation',
runId: 'reopen-promoted-run',
updateKnowledge: async ({ candidateRoot }: { candidateRoot: string }) => {
await writeFile(join(candidateRoot, 'knowledge', 'candidate.md'), '# Candidate\n')
return { applied: true, summary: 'created candidate' }
},
evaluate: passingMetric,
}
const staged = await improveKnowledgeBase(options)
const candidate = knowledgeImprovementCandidateRef(staged)
await promoteKnowledgeCandidate({ root, candidate })
await rm(join(root, 'knowledge', 'candidate.md'))
const liveBeforeReopen = await hashKnowledgeBase(root)

await expect(improveKnowledgeBase(options)).rejects.toThrow(/promoted knowledge base changed/)
expect(await hashKnowledgeBase(root)).toBe(liveBeforeReopen)
await expect(readFile(join(root, 'knowledge', 'candidate.md'), 'utf8')).rejects.toMatchObject(
{ code: 'ENOENT' },
)
})
})

it('rejects evaluator results without provenance', async () => {
await withKb(async (root) => {
await expect(
Expand Down
Loading