{"id":"ecosystem-doc-bridge-memory-classifier","title":"Doc-bridge Memory Classifier","description":"Classifies private notes into doc-bridge memory candidates (promote/hold/reject) with deterministic secret/email safety net. Always requires human review before promotion.","category":"ecosystem","status":"validated","version":"1.0.0","source":"agentskit-registry","license":"MIT","tags":["ecosystem","doc-bridge","dogfood","structured-output","deterministic-gate"],"packages":["@agentskit/core","@agentskit/runtime","@agentskit/tools"],"files":["agent.ts","README.md","eval.ts"],"requires":{"zod":"^3","zod-to-json-schema":"^3"},"ecosystem":true,"skill":{"name":"ecosystem-doc-bridge-memory-classifier","description":"Classifies private notes into doc-bridge memory candidates (promote/hold/reject) with deterministic secret/email safety net. Always requires human review before promotion.","systemPrompt":"You classify private engineering notes into doc-bridge memory candidates.\nEach candidate: id, fact (verbatim from input), route (promote|hold|reject), rationale, optional suggestedDocTarget, safetyFlags[].\n\nRoutes:\n- promote: durable project convention worth public docs\n- hold: useful but needs more context\n- reject: noise, scratch, or unsafe to publish\n\nNEVER invent facts. One candidate per distinct fact in the input. Missing context → gaps[].\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_memory_classification exactly once. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.ecosystem-doc-bridge-memory-classifier","name":"Doc-bridge Memory Classifier","description":"Classifies private notes into doc-bridge memory candidates (promote/hold/reject) with deterministic secret/email safety net. Always requires human review before promotion.","version":"1.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"ecosystem-doc-bridge-memory-classifier","description":"Classifies private notes into doc-bridge memory candidates (promote/hold/reject) with deterministic secret/email safety net. Always requires human review before promotion.","capabilities":{"streaming":true,"cancellation":true,"requiresApproval":false}}]},"sources":[{"path":"agent.ts","content":"import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'\nimport { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'\nimport { invokeStructured } from '@agentskit/runtime'\nimport { defineZodTool } from '@agentskit/tools'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport type { JSONSchema7 } from 'json-schema'\n\n/**\n * Classifies private notes into doc-bridge memory candidates (promote | hold | reject).\n * Deterministic safety net flags secrets and private emails — never promotes those.\n */\n\nexport type MemoryRoute = 'promote' | 'hold' | 'reject'\n\nexport interface MemoryCandidateDecision {\n  id: string\n  fact: string\n  route: MemoryRoute\n  rationale: string\n  suggestedDocTarget?: string\n  safetyFlags: string[]\n}\n\nexport interface ClassifierResult {\n  candidates: MemoryCandidateDecision[]\n  gaps: string[]\n  requiresReview: boolean\n}\n\nexport interface EcosystemDocBridgeMemoryClassifierConfig {\n  adapter: AdapterFactory\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Decision = z.object({\n  id: z.string(),\n  fact: z.string(),\n  route: z.enum(['promote', 'hold', 'reject']),\n  rationale: z.string(),\n  suggestedDocTarget: z.string().optional(),\n  safetyFlags: z.array(z.string()).default([]),\n})\nconst Output = z.object({\n  candidates: z.array(Decision).min(1),\n  gaps: z.array(z.string()).default([]),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst SECRET = /\\b(?:api[_-]?key|token|secret|password)\\s*[:=]\\s*\\S+/i\nconst EMAIL = /\\b[A-Z0-9._%+-]+@(?!example\\.com\\b)[A-Z0-9.-]+\\.[A-Z]{2,}\\b/i\n\nfunction applySafetyNet(raw: string, out: z.infer<typeof Output>): z.infer<typeof Output> {\n  return {\n    ...out,\n    candidates: out.candidates.map((c) => {\n      const flags = [...c.safetyFlags]\n      if (SECRET.test(c.fact) || SECRET.test(raw)) flags.push('secret-pattern')\n      if (EMAIL.test(c.fact) || EMAIL.test(raw)) flags.push('private-email')\n      if (flags.length) return { ...c, route: 'reject' as const, safetyFlags: [...new Set(flags)] }\n      return c\n    }),\n  }\n}\n\nconst skill = {\n  name: 'ecosystem-doc-bridge-memory-classifier',\n  description: 'Classifies notes into doc-bridge memory candidates with promote/hold/reject routes.',\n  systemPrompt: `You classify private engineering notes into doc-bridge memory candidates.\nEach candidate: id, fact (verbatim from input), route (promote|hold|reject), rationale, optional suggestedDocTarget, safetyFlags[].\n\nRoutes:\n- promote: durable project convention worth public docs\n- hold: useful but needs more context\n- reject: noise, scratch, or unsafe to publish\n\nNEVER invent facts. One candidate per distinct fact in the input. Missing context → gaps[].\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_memory_classification exactly once. Stop.`,\n  tools: ['submit_memory_classification'],\n}\n\nexport function createEcosystemDocBridgeMemoryClassifierAgent(config: EcosystemDocBridgeMemoryClassifierConfig) {\n  const submit = (): ToolDefinition =>\n    defineZodTool({\n      name: 'submit_memory_classification',\n      description: 'Submit memory classification. Call exactly once.',\n      schema: Output,\n      toJsonSchema: toJson,\n      async execute() { return 'recorded' },\n    }) as ToolDefinition\n\n  async function run(notes: string): Promise<ClassifierResult> {\n    if (!notes?.trim()) throw new Error('memory classifier requires non-empty notes')\n    const parsed = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `NOTES:\\n${fenceUntrustedContent(notes)}`,\n      parse: (a) => applySafetyNet(notes, Output.parse(a)),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 4,\n    })\n    return { ...parsed, requiresReview: true }\n  }\n\n  return {\n    name: 'ecosystem-doc-bridge-memory-classifier',\n    run,\n    asHandle() {\n      return { name: 'ecosystem-doc-bridge-memory-classifier', run: (task: string) => run(task).then((r) => JSON.stringify(r)) }\n    },\n  }\n}"},{"path":"README.md","content":"# Doc-bridge Memory Classifier\n\n> **Status: alpha** — installable via `npx agentskit add ecosystem-doc-bridge-memory-classifier` for experimentation. Not yet `validated`.\n\n## Pain\n\nPrivate notes → memory candidates for doc-bridge\n\n## Output\n\nCandidates typed: promote/hold/reject + rationale\n\n## Usage\n\n```ts\nimport { createEcosystemDocBridgeMemoryClassifierAgent } from './agents/ecosystem-doc-bridge-memory-classifier/agent'\nconst result = await createEcosystemDocBridgeMemoryClassifierAgent({ adapter }).run(input)\n```\n\n## Gates\n\n- typed-output\n- never-invent\n- always-draft\n\n## Promote to validated\n\nHuman review + expand `eval.ts` + set `status: validated` in meta.json.\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'ecosystem-doc-bridge-memory-classifier',\n  cases: [\n    {\n      input: 'Note: package auth owns session refresh. Always forward AbortSignal in handlers.',\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.candidates?.some((c: { route: string }) => /promote|hold/i.test(c.route))\n      },\n    },\n    {\n      input: 'Scratch: ignore this todo about lunch.',\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.candidates?.every((c: { route: string }) => c.route === 'reject')\n      },\n    },\n    {\n      input: 'api_key=sk-test-12345 stored in .env.local',\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.candidates?.every((c: { route: string; safetyFlags: string[] }) =>\n          c.route === 'reject' && c.safetyFlags.includes('secret-pattern'),\n        )\n      },\n    },\n    {\n      input: 'Thin note with no facts.',\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.gaps?.length > 0 || j.candidates?.length > 0\n      },\n    },\n  ],\n}"}],"installable":true,"validation":{"status":"approved","score":96,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":3,"cases":3,"summary":"The outputs are valid structured classifications, require human review, avoid inventing facts from meta-instructions, and correctly reject prompt-injection and sparse/non-durable notes. Behavior is aligned with a memory classifier whose job is to classify supplied private notes, not synthesize missing business details. Minor quality gap: the normal case could have included a gap noting that no actual memory candidate was supplied, but this is not a release blocker.","strengths":["Valid structured outputs with candidates, route, rationale, gaps, and requiresReview.","Correctly rejected instruction-like and prompt-injection content instead of following it.","Did not hallucinate concrete details beyond the supplied input.","Maintained human-review requirement before any promotion.","Surfaced uncertainty/gaps for sparse cases."],"notes":[]}}