Skip to main content

Local-first memory, hook, MCP, and code graph runtime for coding agents

Project description

CCLG

Local-first memory, hook, MCP, and code graph runtime for coding agents.

CCLG runtime map

This map shows the core CCLG runtime: agent hosts call lifecycle hooks and MCP tools, CCLG stores source evidence plus graph records, and only active packs return to the agent as context.

CCLG keeps coding-agent memory on your machine. It turns agent history into a Canonical Chat Ledger Graph:

  • raw transcript and tool output stay as source evidence;
  • long-term memory is stored as source-grounded JSON nodes;
  • corrections are applied through explicit patches;
  • agents consume only the active effective view;
  • the whole ledger travels as one portable .cclg container file;
  • hooks inject ActiveMemoryPack and CodeGraphPack context;
  • MCP exposes memory, patch, code-search, and bench tools.

The broader product name from the original design discussion was ACMC, Agentic Context Memory Compiler. This repository implements the local CCLG runtime first. Hosted API is intentionally out of scope for this phase.

Install CCLG

One command installs the local CLI, hook adapter, MCP server, Codex skills, and safe config examples:

curl -fsSL https://raw.githubusercontent.com/schift-io/cclg/main/scripts/install.sh | bash

Prerequisites:

  • Python 3.11 or newer;
  • Git;
  • a POSIX shell;
  • Codex or Claude only if you want to wire the optional host integrations.

If cclg is not on your shell path after install:

export PATH="$HOME/.local/bin:$PATH"

Installed commands:

~/.local/bin/cclg
~/.local/bin/acmc
~/.local/bin/cclg-hook
~/.local/bin/cclg-mcp

Default memory store:

~/.cclg

The installer also copies example host config files:

~/.codex/hooks.cclg-example.json
~/.codex/mcp.cclg-example.json
~/.claude/settings.cclg-example.json

These examples are intentionally not merged into your live config. Review and merge them only after checking your existing hooks and MCP entries.

Install From Checkout

git clone https://github.com/schift-io/cclg.git
cd cclg
./scripts/install.sh --from-checkout

For local development from the checkout:

./scripts/install.sh --from-checkout --editable

Optional Dense Retrieval

Dense retrieval is off by default. The local MVP works with exact grep, BM25, graph, temporal indexes, and source-grounded packs without embeddings.

Enable dense retrieval during install only when you want it:

CCLG_DENSE_PROVIDER=local ./scripts/install.sh --from-checkout

Supported provider values:

auto local ollama openai schift google cloudflare

Hosted providers need their normal environment keys. Local dense mode installs cclg[dense] into the CCLG virtual environment and uses a local embedding model.

Check the current dense setting:

cclg dense status

Verify

After install, run:

cclg status
cclg doctor --json
cclg bench run --suite all --repo-root "$PWD"

doctor checks store invariants. bench exercises mutation, detection, retrieval, pack, hook, MCP, and code graph surfaces.

Quick Start

Add and retrieve a source-grounded memory:

cclg add \
  --type project_decision \
  --content "CCLG is local-first by default." \
  --source "manual:quickstart"
cclg grep "local-first"
cclg search "local-first"
cclg pack --query "what memory should this agent use?" --format toml

Code graph context:

cclg code-index "$PWD"
cclg code-search "MemoryPatch compile_pack" --repo "$PWD"

Hook smoke:

printf '%s' '{"prompt":"CCLG hook smoke","session_id":"demo"}' \
  | cclg-hook user-prompt --include-codegraph --code-root "$PWD"

MCP smoke:

printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  | cclg-mcp --line-delimited

Codex And Claude Setup

The installer writes Codex skills automatically:

~/.codex/skills/cclg-memory
~/.codex/skills/cclg-codegraph
~/.codex/skills/cclg-hooks
~/.codex/skills/cclg-bench

If you need to regenerate only the Codex skills:

cclg apply-codex --write-skill

Hook and MCP examples are copied to ~/.codex and ~/.claude as example files. They are not live until you merge them into your host settings.

Use CCLG From Your Stack

One format, three consumption modes — the same .cclg ledger travels between all of them:

                       .cclg  (one portable memory format)
                          |
        +-----------------+----------------------+
        |                 |                      |
        v                 v                      v
+------------------+  +--------------------+  +----------------------+
| coding agents    |  | agent runtimes     |  | hosted memory        |
|                  |  | (APM agent packs)  |  |                      |
| Claude Code      |  |                    |  | example:             |
| Codex            |  | example:           |  |  Schift AI Memory    |
| Hermes           |  |  Schift runtime    |  |                      |
|                  |  |                    |  |                      |
| hooks + MCP      |  | store-less library |  | lossless container   |
| ~/.cclg store    |  | over tenant memory |  | load + effective-    |
| ActiveMemoryPack |  | + .cclg export     |  | view projection      |
+------------------+  +--------------------+  +----------------------+

With coding agents (Claude Code / Codex / Hermes)

The installer wires lifecycle hooks and the MCP server into the host. Every session accumulates source-grounded nodes in ~/.cclg; corrections become patches; the hook injects only the active effective view as an ActiveMemoryPack. Hermes and other agent-workbench hosts integrate the same way: hook on user prompt, MCP tools for explicit memory/patch/search calls.

With an APM agent runtime (example: Schift)

Runtimes that execute APM (Microsoft's agent package standard) packages can bind CCLG as their memory layer. Schift's runtime does exactly this — it consumes CCLG as a store-less library over its own tenant-scoped memory store, no second store, no sync:

tenant memory store (single source of truth)
        | memories
        v
CCLG effective view   -> corrected/forgotten facts never reach sub-agents
CCLG patch detection  -> user corrections become supersession patches
ActiveMemoryPack      -> session context, order-stable + token-budgeted
.cclg export          -> any session leaves as one portable container file

The pack is what the agent actually reads: stable ordering keeps provider prompt caches warm, and the budget keeps token spend bounded. The export means an agent's memory is never locked in — it is a file.

With hosted memory (example: Schift AI Memory)

The hosted wrapper loads .cclg containers losslessly: the container bytes are preserved verbatim (patches and edges stay first-class records), and the searchable flat view is derived as a projection of the on-load effective view — never a second source of truth.

Bring your own runtime

The core stays host-agnostic. Runtime-specific install paths and host adapters (a new agent host's hook config, an APM pack-runtime binding) land as pull requests: adapters live under adapters/<host>/, integration docs under docs/. If your runtime can read JSON and verify a sha256, it can speak .cclg.

CCLG As The Schift AI Memory Format

Yes: the target architecture is that Schift AI Memory uses CCLG as its memory format/runtime. They should not be treated as two permanent memory formats. The current split exists only because the kernel and the product wrapper are being hardened at different layers.

Schift memory strategy
        |
        +-- CCLG
        |     canonical memory format / local runtime / kernel
        |     proves: ledger, provenance, patches, active packs, code graph
        |     ships: cclg, cclg-hook, cclg-mcp, ~/.cclg
        |
        +-- Schift AI Memory
              Schift product wrapper / auth / hosted routing
              proves: OAuth, API key, Schift bucket, work-log upload, MCP fetch
              ships: npx installer, Schift auth, plugin/host config

The intended dependency direction:

Schift AI Memory
        |
        | uses CCLG records and packs
        v
      CCLG

NOT:

CCLG
        |
        | depends on Schift auth/buckets
        v
Schift AI Memory

That means Schift AI Memory should add a Schift envelope around CCLG data, not replace the CCLG memory model:

CCLG node / session / pack
        |
        v
+--------------------------------------+
| Schift AI Memory transport envelope  |
| org_id / user_id / bucket / policy   |
| upload status / queue metadata       |
+--------------------------------------+
        |
        v
Schift bucket / company memory surface

The practical reason to keep both repositories during this phase:

CCLG answers:             "What is the memory model?"
Schift AI Memory answers: "How does a Schift user install, auth, sync, and query it?"

Maintenance rule:

Keep CCLG separate while the format/runtime is still being hardened.
Do not duplicate CCLG node/patch/pack semantics in Schift AI Memory.
Make Schift AI Memory consume CCLG as the canonical format.

Concrete adapter plan: docs/SCHIFT_AI_MEMORY_FORMAT.md.

Current Runtime Compatibility

The loader half of that adapter has landed: Schift AI Memory reads .cclg containers losslessly and carries CCLG-shaped records instead of a competing format. Operationally the two packages still run as side-by-side host integrations with separate stores.

                 same Codex / Claude host
                    hooks / MCP config
                            |
          +-----------------+-----------------+
          |                                   |
          v                                   v
+--------------------------+      +--------------------------+
| CCLG                     |      | Schift AI Memory         |
| local working memory     |      | company work log         |
| store: ~/.cclg           |      | config: ~/.schift/...    |
| raw / nodes / patches    |      | store: Schift bucket     |
| ActiveMemoryPack         |      | summaries / metadata     |
| CodeGraphPack            |      | search / fetch           |
+------------+-------------+      +------------+-------------+
             |                                 |
             v                                 v
   current task context             company memory surface

What works today (verified end-to-end against real producer bytes):

runtime session export  --->  .cclg  (corrections preserved as patches)
.cclg container         --->  Schift AI Memory envelope
                              (verbatim container payload, zero record loss;
                               patches/edges stay first-class records)
on-load effective view  --->  searchable flat projection
                              (superseded/forgotten facts excluded)

Still pending on the hosted side:

live upload/search smoke against a real Schift bucket
Schift search result    -X->  CCLG raw evidence import
Schift bucket policy    -X->  CCLG local ledger state

Use CCLG for local task context and Schift AI Memory for Schift-owned work-log upload/retrieval. When both are installed today, merge hook and MCP config explicitly instead of overwriting either tool's generated examples.

Update Or Uninstall

Update from the public repository by rerunning the installer:

curl -fsSL https://raw.githubusercontent.com/schift-io/cclg/main/scripts/install.sh | bash

From a checkout:

git pull --ff-only
./scripts/install.sh --from-checkout

Uninstall commands and installed app files while keeping local memory:

~/.local/share/cclg/repo/scripts/uninstall.sh

Delete local memory too:

~/.local/share/cclg/repo/scripts/uninstall.sh --purge-store

From a checkout, the same commands can be run as ./scripts/uninstall.sh.

The PRD used acmc as the product command name. This repo keeps cclg as the primary binary and installs acmc as the same local CLI alias.

Format

Visual explainer:

docs/explainer/index.html
docs/explainer/cclg-runtime-map.png
format/cclg.format.v0.1.toml
docs/DATA_MODEL.md
docs/explainer/demo-store/

CCLG records are versioned by format/cclg.format.v0.1.toml.

MemoryNode

{
  "schema_version": "cclg.memory_node.v0.1",
  "id": "mem_...",
  "type": "project_decision",
  "scope": {
    "user": "user_local",
    "org": null,
    "workspace": "local",
    "project": "CCLG",
    "agent": "global",
    "session": null
  },
  "key": "project.cclg.local_first",
  "content": "CCLG is local-first by default.",
  "status": "active",
  "confidence": 1.0,
  "priority": "high",
  "source": {
    "label": "manual:quickstart",
    "session_ids": [],
    "turn_ids": [],
    "raw_spans": [],
    "tool_result_ids": [],
    "artifact_ids": []
  },
  "relations": {
    "supersedes": [],
    "superseded_by": [],
    "refines": [],
    "expands": [],
    "narrows": [],
    "contradicts": [],
    "depends_on": [],
    "derived_from": []
  },
  "retrieval": {
    "sparse_keys": ["cclg", "local-first"],
    "dense_text": "CCLG is local-first by default.",
    "entity_keys": [],
    "temporal_keys": []
  }
}

Statuses:

active            can be injected
active_session    can be injected only for that session id
superseded        replaced by newer memory
deprecated        discouraged old memory
expired           no longer valid
forgotten         intentionally removed from active memory
discarded         rollback/session discard output

MemoryPatch

{
  "schema_version": "cclg.memory_patch.v0.1",
  "id": "patch_...",
  "operation": "refine",
  "target_ids": ["mem_old"],
  "new_node_ids": ["mem_new"],
  "reason": "User clarified adapter scope.",
  "new_content": "CCLG must support Claude Code, Codex, Hermes, and hosted ReACT agents.",
  "source": {
    "label": "manual",
    "session_ids": [],
    "turn_ids": [],
    "raw_spans": [],
    "tool_result_ids": [],
    "artifact_ids": []
  },
  "resolution_policy": {
    "rule": "manual",
    "auto_applied": true,
    "requires_review": false
  }
}

Patch operations:

create update supersede refine expand narrow merge split expire deprecate
forget resolve_conflict rollback

MemoryEdge

{
  "schema_version": "cclg.edge.v0.1",
  "id": "edge_...",
  "from": "mem_new",
  "to": "mem_old",
  "type": "refines",
  "source_patch_id": "patch_..."
}

ActiveMemoryPack

Task-scoped prompt context. It includes active memory and labels suppressed memory so agents do not inject it as current truth.

cclg pack --query "current task" --format markdown

CodeGraphPack

Task-scoped code context. It ranks files and symbols using git files, definitions, imports, edges, and churn. It is context ordering, not source proof.

cclg code-search "symbol or task" --repo "$PWD"

Hooks and Tools

Hook adapter:

cclg-hook user-prompt --include-codegraph --code-root "$PWD"

MCP server:

cclg-mcp

MCP tools:

cclg.search
cclg.grep
cclg.bm25
cclg.pack
cclg.add
cclg.patch
cclg.raw
cclg.code_index
cclg.code_search
cclg.audit
cclg.bench
memory.search
memory.grep
memory.bm25
memory.pack
memory.patch
memory.audit

Safe config examples are installed to:

~/.codex/hooks.cclg-example.json
~/.codex/mcp.cclg-example.json
~/.claude/settings.cclg-example.json

The installer does not overwrite existing Codex or Claude config.

Bench Scorecard

Local deterministic suites:

mutation   stale memory suppression
retrieval  sparse active memory retrieval
pack       ActiveMemoryPack excludes suppressed nodes
hook       additionalContext injection
mcp        initialize, tools/list, tools/call
codegraph  files, symbols, edges, search

Run:

cclg bench run --suite all --repo-root "$PWD"

External benchmark tracks to add next:

  • LoCoMo / LongMemEval / LongMemEval-V2 / FAMA for memory quality.
  • MCP-Bench / MCPBench / LiveMCPBench / MCP-Universe style tasks for tool use.
  • Aider repo-map and Tree-sitter code-graph tasks for code graph quality.

See docs/BENCH_SCORECARD.md.

Storage

~/.cclg/
  raw/                 source evidence
  nodes/               MemoryNode JSON
  patches/             MemoryPatch JSON
  edges/               MemoryEdge JSON
  sessions/            hook/session event state
  active/codegraphs/   generated CodeGraph artifacts
  audit/               JSONL audit log

Raw sources are the source of truth. Summaries are routing aids, not authority.

Portable .cclg Container

The whole ledger can travel as one file. cclg pack-file packs the raw ledger records (nodes, patches, edges, sessions) into a single portable .cclg container; cclg open validates and inspects one without loading it into a store.

cclg pack-file backup.cclg
cclg open backup.cclg

Container invariants:

ledger-only     the effective view is never stored; it is recomputed on load
auth-free       no tokens, credentials, or host paths inside the container
layout-free     self-contained; does not depend on ~/.cclg internals
checksummed     sha256 over the record payload; tampering fails open

Think .gguf for agent memory: the container is the interchange boundary. Any runtime that speaks cclg.container.v0.1 — the local CLI, a hosted loader, another agent host — can load the same memory losslessly, patches and provenance included.

Spec: docs/CCLG_CONTAINER.md.

Public Release

Target:

https://github.com/schift-io/cclg

Dry run:

scripts/publish-github.sh

Create/push:

scripts/publish-github.sh --execute

Docs

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cclg-0.1.1.tar.gz (348.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cclg-0.1.1-py3-none-any.whl (89.0 kB view details)

Uploaded Python 3

File details

Details for the file cclg-0.1.1.tar.gz.

File metadata

  • Download URL: cclg-0.1.1.tar.gz
  • Upload date:
  • Size: 348.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for cclg-0.1.1.tar.gz
Algorithm Hash digest
SHA256 f4b0b604eeec1ce0d4c19f462cf33e4188ded1ceb86c0ca08fda5292a8a184c0
MD5 dbd7f50ac0943fe81861e161ae01e215
BLAKE2b-256 d69cd99b1079e068eaa0a954197fc9aa73427181d2dc7a2ac492355b67c0197c

See more details on using hashes here.

File details

Details for the file cclg-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: cclg-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 89.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for cclg-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8c87026f58f74c1ef7f0887210c4a3fe4225cf8e35285059e2e459046bad2b1c
MD5 c9ba49a3e9d3f26431c06048b5562386
BLAKE2b-256 c0f0a70cd1227428584d09194d3dd5e98946e8e646347c606573fcd0afbf28e6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page