Aller au contenu

Pattern Materializer

Un Materializer transforme un artefact logique (agent, hook, skill) en fichiers sur disque dans un dossier projet. Le pattern est codifié dans ADR 0003 : le core ne fait jamais d’écriture directe pour ces artefacts ; il passe par un port dédié.


Sans Materializer, le core écrirait directement les artefacts agents et hooks dans le dossier Claude Code du projet. Cela créerait plusieurs problèmes :

  • Couplage : le core connaîtrait le format de fichier d’un agent (Markdown YAML, JSON, etc.) — ce n’est pas son métier.
  • Frontières : impossible de mocker l’écriture pour les tests sans patcher fs.
  • Évolution : changer le format (par exemple ajouter une signature) demanderait de toucher au core.

Le Materializer isole tout ça derrière un port :

core/ports/outbound/agent-workspace-materializer.ts
export interface AgentWorkspaceMaterializer {
materialize(input: MaterializeAgentInput): Promise<MaterializedAgent>;
unmaterialize(input: { projectPath: string; sourceId: string }): Promise<void>;
list(projectPath: string): Promise<readonly MaterializedAgent[]>;
}

Le use-case buildForCatalogue consomme ce port. L’implémentation concrète ClaudeAgentWorkspaceMaterializer connaît, elle, le format Claude Code.


MaterializerCibleFormat de sortie
ClaudeAgentWorkspaceMaterializeragents/<slug>.md sous <projectPath>/.claudeMarkdown + frontmatter YAML
HookWorkspaceMaterializerhooks/<name>.json sous <projectPath>/.claudeJSON
SkillWorkspaceMaterializerskills/<name> sous <projectPath>/.claudeDossier + manifest

Tous écrivent dans le dossier Claude Code du projet (<projectPath>/.claude), jamais ailleurs. L’allowlist Filesystem valide les chemins.


  • Vous voulez exposer un artefact logique (par exemple “policy Mission Control”) en tant que fichier consommable par un outil externe (un éditeur, un agent IA, un CI).
  • Le format de sortie peut évoluer sans que le core en soit affecté.
  • Plusieurs cibles peuvent exister (par exemple un agent pour Claude Code + un agent pour Codex CLI → deux Materializers).

Évitez le pattern Materializer pour des écritures purement internes (sessions chat, mémoire) — celles-ci passent par leur store dédié.


Install agent (use-case forCatalogue.install)
[Lit profil depuis Cortex]
[Materializer.materialize → écrit agents/<slug>/ sous <project>/.claude]
[Met à jour <project>/.arka-deck/agents/installed.json]
EventBus → 'agent.installed' (async)
Remove agent (forCatalogue.remove)
[Materializer.unmaterialize → supprime agents/<slug>/ sous <project>/.claude]
[Met à jour installed.json]
EventBus → 'agent.removed' (async)

Le suivi installed.json permet une purge sûre : seuls les fichiers tracés sont supprimés, jamais les artefacts de l’utilisateur (autres agents non-arka-deck sous .claude).


L’implémentation concrète est testable comme tout adapter. Pour les use-cases qui le consomment, on injecte un FakeAgentWorkspaceMaterializer :

const fakeMaterializer: AgentWorkspaceMaterializer = {
async materialize(input) { return { /* ... */ }; },
async unmaterialize() {},
async list() { return []; },
};
const forCatalogue = buildForCatalogue({
catalogueClient,
agentMaterializer: fakeMaterializer,
// ...
});