Skip to content

Rules Portability

Manage your project’s AI instructions in one place. SessionFS keeps a canonical rules record per project and compiles it into the tool-specific files each AI agent reads — CLAUDE.md, codex.md, .cursorrules, .github/copilot-instructions.md, and GEMINI.md — so instructions stay consistent across every tool your team uses.

Every captured session also records the rules and instruction artifacts that shaped it, so you can always answer: what was the agent told when this session happened?

AI-heavy teams maintain the same project conventions in five different places:

ToolInstruction File
Claude CodeCLAUDE.md
Codexcodex.md
Cursor.cursorrules
GitHub Copilot.github/copilot-instructions.md
Gemini CLIGEMINI.md

Each format drifts. One file gets updated, the others don’t. A developer who switches tools gets an agent that doesn’t know the project’s conventions, security rules, or architecture decisions. Sessions generated under different rule sets are impossible to compare after the fact.

canonical project rules (SessionFS)
+ active knowledge claims
+ project context sections
│ sfs rules compile
CLAUDE.md · codex.md · .cursorrules · copilot-instructions.md · GEMINI.md
agent sessions shaped by those rules
sessions captured with rules_version + rules_hash + instruction_artifacts

The canonical record is the source of truth. Compiled tool files are projections — they carry a SessionFS marker so future compiles know they are safe to overwrite.

Terminal window
# From inside your git repo
sfs rules init # pick tools, seed canonical rules
sfs rules edit # edit static preferences in $EDITOR
sfs rules compile # write CLAUDE.md / codex.md / etc.
git add CLAUDE.md codex.md .cursorrules .github/copilot-instructions.md GEMINI.md
git commit -m "Add project rules"

Teammates who clone the repo see the compiled files immediately. Even developers who don’t run SessionFS still get consistent agent behavior — the compiled rule files are just text, readable by every tool that natively supports them.

Committing compiled files is deliberate:

  • Fresh clones get consistent agent behavior with zero setup
  • Developers who don’t run SessionFS still see the project contract
  • Teammates can review rule changes in normal code review
  • Compiled files embed a SessionFS marker so future sfs rules compile knows they’re safe to regenerate

Local-only mode adds the compiled paths to .gitignore. The canonical record is still shared via sfs rules push / sfs rules pull.

You can switch between modes later by editing .gitignore by hand.

Seeds canonical rules for the current project.

sfs rules init [--local-only]

At init time, SessionFS preselects enabled tools using this precedence:

  1. Existing repo rule files — if CLAUDE.md, .cursorrules, etc. are already present, the corresponding tools are preselected with reason file present.
  2. Recent captured tool usage — tools with sessions captured in the last 90 days are preselected with reason recent usage.
  3. User picker — if neither signal is present, SessionFS prompts you to pick tools interactively.

Only the five supported tools are considered in v0.9.9: claude-code, codex, cursor, copilot, gemini. The picker shows a reason for each preselection so you can override.

If exactly one unmanaged recognized rule file exists (for example, a hand-written CLAUDE.md), sfs rules init offers to import it as the canonical seed. Multiple unmanaged files are never auto-merged — you must pick one source explicitly.

Opens the canonical static_rules document in $EDITOR.

sfs rules edit

This is the primary surface for editing project conventions. Save, exit, then run sfs rules compile to regenerate tool files.

sfs rules show

Shows current canonical version, enabled tools, knowledge injection config, context injection config, and whether compiled outputs are in sync.

Compiles canonical rules into the tool-specific files.

sfs rules compile [--tool TOOL] [--dry-run] [--force]
OptionDescription
--tool TOOLCompile for a single tool only (claude-code, codex, cursor, copilot, gemini)
--dry-runShow what would be written without touching disk
--forceOverwrite unmanaged files (dangerous — see below)

Compilation is deterministic — the same inputs always produce the same outputs. A new rules_versions row is only created when at least one compiled output changes by hash. Recompiling with no input changes is a no-op.

SessionFS will only overwrite a rule file it considers managed. A file is managed if:

  • it was created by sfs rules compile, or
  • it contains the SessionFS managed marker, or
  • it was explicitly imported via sfs rules init

If a target path already exists and is not managed, compilation refuses to overwrite it:

Terminal window
$ sfs rules compile
ERROR CLAUDE.md exists and is not managed by SessionFS.
Run 'sfs rules init' to import it, or re-run with --force.

Every compiled file embeds a small marker that names SessionFS as the generator, records the canonical version, and stores the content hash:

<!-- sessionfs:managed version=4 hash=sha256:9a1c… -->
<!-- Regenerate with: sfs rules compile -->

The marker is lightweight and tool-compatible. It is what allows future compiles to know the file is safe to overwrite.

Push the canonical record and latest compiled version to the SessionFS API.

sfs rules push

Uses optimistic concurrency. If the remote is ahead, the push fails with 409 Conflict and you must sfs rules pull and retry.

Pull canonical rules from the SessionFS API into the local project.

sfs rules pull

Fetches the current canonical record and writes it into the local rules config. Run sfs rules compile afterwards to regenerate tool files from the pulled rules.

Compiled output is assembled from three inputs:

  1. Static rules — the canonical preferences you edit with sfs rules edit.
  2. Project knowledge claims — active, durable claims from your project’s knowledge base.
  3. Project context sections — selected sections of the compiled project context document.

Enabled by default, knowledge claims are injected descriptively — as project facts, not as commands.

Default TypesNot Injected
conventionbug
decisiondiscovery
note

Only active, durable claims are injected. A claim is eligible when:

  • claim_class = 'claim' (not a hypothesis or raw observation)
  • not dismissed
  • not superseded
  • freshness is in an active state

Good — descriptive:

Project fact: API routes live in src/server/routes/.

Bad — prescriptive:

Always use src/server/routes/ because the knowledge base says so.

Preferences in static_rules are prescriptive (those are your team’s rules). Knowledge is descriptive (those are project facts the agent should know).

Selected project-context sections are appended to the compiled output. Defaults:

  • overview
  • architecture

Configure which sections to include by editing the canonical rules record.

Each compiler knows its approximate token ceiling. Large-budget tools receive the full combined payload; smaller-budget tools get a progressively condensed form. Compilation remains deterministic — if the budget forces a trim, the trim is reproducible.

No LLM is required for rules compilation in v0.9.9.

sfs rules init does not enable every possible tool. Only explicitly selected or inferred tools are compiled. This avoids generating CLAUDE.md for a team that has never used Claude Code, or .cursorrules for a team that doesn’t touch Cursor.

SessionFS can inject compiled rules through Claude Code’s SessionStart hook instead of (or alongside) a CLAUDE.md file. Every time Claude Code starts a session, the hook runs sfs rules emit and pipes the latest compiled rules into the system prompt.

File-based compileHook-based injection
Toolsclaude-code, codex, cursor, copilot, geminiclaude-code only
Source of truthCLAUDE.md on disklocal rule cache (sfs rules compile / sfs rules pull)
Freshnessonly as fresh as the last commitrefreshed every Claude Code startup
File on diskyesno
Commit requiredyes (default)no

The hook reads the local rule cache that sfs rules compile and sfs rules pull populate. It does not hit the network on Claude Code startup, so there is no latency cost and no offline failure mode. If the cache is empty, the hook emits an empty payload and exits cleanly — Claude Code starts normally.

Terminal window
sfs hooks install --for claude-code

By default the hook is written to ~/.claude/settings.json (--user scope) and applies to every Claude Code session on this machine. To install a project-scoped hook that ships with the repo, use --project:

Terminal window
sfs hooks install --for claude-code --project

That writes to .claude/settings.json in the current repo root, where it can be committed and shared with teammates. Project-scope hooks layer on top of user-scope hooks.

Install is idempotent — running it twice does not duplicate the entry. The SessionFS-managed hook block carries an "sfs:managed": true sentinel so uninstall can find and remove it without touching other user-defined hooks.

Terminal window
sfs hooks status

Lists which scopes have the hook installed across all supported tools. Tools without native hook support are listed as N/A.

Terminal window
sfs hooks uninstall --for claude-code

Removes the SessionFS-managed entry from settings.json and leaves any other user-defined hooks untouched. Add --project to remove the project-scope hook, --force to skip the confirmation prompt.

Two reasonable workflows:

  • Hook only. Delete the committed CLAUDE.md (or never compile one) and rely on the hook. Each developer’s machine pulls rules with sfs rules pull and re-emits on every startup. No file management, no commits, always fresh — but only works for Claude Code users.
  • File only. Skip the hook entirely. Compile to CLAUDE.md / codex.md / .cursorrules / copilot-instructions.md / GEMINI.md, commit them, and let every tool read its native file. Works for every supported tool and every developer, regardless of whether they have SessionFS installed.

A hybrid (committed CLAUDE.md for team baseline + the hook for personal freshness) is supported but produces duplicated context inside Claude Code.

sfs rules emit --tool claude-code --format hook prints JSON in Claude Code’s hook output spec:

{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "<the compiled rules content>"
}
}

--format file prints the plain compiled body — the same text that would land in CLAUDE.md. Use --format file when you want to pipe the compiled content into another tool or inspect it from the shell.

The hook is offline by design. Run sfs rules pull (or sfs rules compile) when you want to refresh the cache.

sfs resume preflights the target tool’s project rules file from the current canonical SessionFS rules before launching the tool. The session transfer carries the conversation; resume-time sync makes sure the resumed agent reads the same project contract the rest of the team is using.

Rules sync runs for these four resume-capable tools:

  • claude-code — writes CLAUDE.md
  • codex — writes codex.md
  • copilot — writes .github/copilot-instructions.md
  • gemini — writes GEMINI.md

Cursor, Cline, Roo Code, and Amp are capture-only — they are not resume targets, so resume-time sync does not apply.

Terminal window
sfs resume ses_abc123 --in codex
  1. Resolve the source session and target project path.
  2. Read the source session’s rules provenance from manifest["instruction_provenance"] (if present).
  3. Resolve the current project and its canonical rules.
  4. Compile only the target tool from current canonical rules and write the file (subject to the managed-file safety cases below).
  5. Launch the target tool’s resume.

Typical stderr output:

Source session used rules v3 (sessionfs).
Current project rules are v5.
Synced codex.md from SessionFS rules v5.

The default is always the project’s current canonical rules, not the source session’s historical version. Source session provenance is displayed on stderr for awareness, but it is never used as the runtime default. If you need to know what shaped the original session, check the provenance fields in the session manifest or the dashboard session detail view.

Historical replay (--rules-from-session and --rules-version N) is deferred to v0.9.10. The underlying provenance data is persisted starting in v0.9.9.

CaseStateBehavior
ATarget file missingCompile and write
BTarget file exists, SessionFS-managedOverwrite (refresh)
CTarget file exists, unmanagedDo not overwrite; warn on stderr; resume continues
DTarget file exists, unmanaged, --force-rules passedOverwrite with SessionFS-managed content

A file is managed if it carries the SessionFS managed marker, was created by sfs rules compile, or was explicitly imported via sfs rules init. This is the same ownership model used by sfs rules compile — resume does not implement a second policy.

FlagDescription
--no-rules-syncSkip the preflight entirely for this invocation. Per-invocation only — not persisted.
--force-rulesAllow Case D: overwrite an unmanaged target-tool rules file with SessionFS-managed content. Applies only to the target tool file for this resume.

When the source session’s manifest includes instruction_provenance, resume prints a short summary:

  • Source session used rules v3 (sessionfs).
  • Source session used manual rules (hash only).
  • Source session used mixed rule sources.
  • Source session had no recorded rules provenance.

When the manifest has no provenance entry (older captures, or the source tool never ran with a rules file), the output shows Source session had no recorded rules provenance. Provenance display is informational only — it does not change what gets written.

Rules sync is skipped silently when:

  • the target tool is not one of the four resume targets above
  • --no-rules-sync was passed
  • the target path does not resolve to a git-backed project SessionFS can map to a project record

Rules sync is skipped with a short info message when canonical rules are not meaningfully initializedenabled_tools is empty, static_rules is empty, and tool_overrides is empty:

No initialized SessionFS project rules found for this repo.
Continuing resume without rules sync.
Launching gemini --resume latest ...

Resume-time sync compiles the target tool even if it is not listed in the canonical enabled_tools set. Rationale: the user explicitly chose to resume in that tool, and rules portability should carry behavior across tool switches without requiring pre-enabling every possible tool.

The compile used by resume is partial and non-versioning:

  • it writes only the target tool’s file
  • it does not create a new rules_versions history row
  • it does not mutate canonical enabled_tools

If you want the tool added to enabled_tools permanently, run sfs rules init or edit the canonical record directly.

Rules sync during resume is best-effort and non-fatal. If project lookup, compile, or file write fails, SessionFS prints a warning on stderr and continues the resume. sfs resume always exits 0 for rules-sync failures — the session transfer is more important than rules sync, and a rules preflight issue must not block cross-tool resume.

There is no --dry-run flag on sfs resume. For a preview of what a target tool’s file would look like under current canonical rules, use:

Terminal window
sfs rules compile --tool codex --dry-run

That command shows the exact content resume would write, without touching disk.

Every captured session records what guided the agent at the time. Four fields are persisted on the session row:

FieldValue
rules_versionCanonical version ID when SessionFS-managed, else null
rules_hashSHA-256 of the tool’s rule file at capture time
rules_sourcesessionfs, manual, mixed, or none
instruction_artifactsList of artifact objects (see below)

rules_source = mixed means both SessionFS-managed and manual/global artifacts shaped the session (for example, a project CLAUDE.md managed by SessionFS alongside a hand-written global ~/.claude/CLAUDE.md).

Not all artifact types are equally reconstructable. SessionFS captures full content for managed rules only; everything else is recorded by hash for identity/change verification.

Artifact typeWhat’s capturedReconstructable?
SessionFS-managed project rulesversion + hash; full text in rules_versionsYes — any historical version
Unmanaged project rules (e.g. hand-written CLAUDE.md)path + hash + source + scopeNo — hash only, no content stored
Global rules/settings (e.g. ~/.claude/CLAUDE.md)path + hash (when SFS_CAPTURE_GLOBAL_RULES=on)No — hash only
Nested project agents/skills (.agents/, .claude/commands/, .claude/skills/, etc.)path + hash per file (up to 30 per directory root)No — hash only

Each artifact is a small JSON object:

{
"artifact_type": "rules_file",
"path": "CLAUDE.md",
"scope": "project",
"source": "sessionfs",
"hash": "sha256:9a1c…"
}
FieldValues
artifact_typerules_file, agent, skill, settings
scopeproject, global
sourcesessionfs, manual, tool

Scope differs by artifact type:

  • Managed compiled rule files (source: sessionfs) — version + hash. Full text is stored immutably in rules_versions, so any historical version can be reconstructed.
  • Unmanaged project artifacts (e.g. a hand-written CLAUDE.md) — path + hash + source + scope only. Content is not stored. You can verify identity or detect changes by hash, but cannot reconstruct the original text.
  • Global artifacts (user home directory rules, global agent files) — hash only, discovered best-effort. Content is never stored. Suppressed entirely when SFS_CAPTURE_GLOBAL_RULES=off.
  • Nested skills/agents — project-local files under .agents/, .claude/commands/, .claude/skills/, and similar directories are discovered recursively. Capture is bounded at 30 files per directory root and uses deterministic sorted order. Only path + hash are recorded; content is not stored.

Capture is best-effort and non-fatal. If SessionFS cannot classify an artifact, it records what it can and moves on. Session capture never fails because of missing or malformed rule files.

Global rule files can leak personal conventions from a developer’s home directory into shared session records. To suppress all global instruction artifact capture:

Terminal window
export SFS_CAPTURE_GLOBAL_RULES=off

When set, SessionFS skips hashing any artifact with scope: global. Project-scope artifacts continue to be captured normally. Default is on.

All endpoints require authentication.

Returns the canonical rules record.

{
"id": "rls_a1b2",
"project_id": "prj_xyz",
"version": 4,
"static_rules": "…markdown…",
"include_knowledge": true,
"knowledge_types": ["convention", "decision"],
"knowledge_max_tokens": 2000,
"include_context": true,
"context_sections": ["overview", "architecture"],
"context_max_tokens": 2000,
"enabled_tools": ["claude-code", "codex", "cursor"],
"tool_overrides": {},
"updated_at": "2026-04-13T12:00:00Z"
}

Update the canonical rules record. Uses optimistic concurrency — send the current version you read, and the server will reject stale writes with 409 Conflict.

{
"version": 4,
"static_rules": "…new markdown…",
"enabled_tools": ["claude-code", "codex", "cursor", "gemini"]
}

Responses:

  • 200 OK — update accepted, returns new record with incremented version
  • 409 Conflict — your version is stale; fetch, merge, retry

The server never does last-write-wins.

Trigger a server-side compile. Returns the compiled outputs and (if any output hash changed) creates a new rules_versions row.

{
"version": 5,
"changed": true,
"compiled_outputs": {
"claude-code": { "path": "CLAUDE.md", "content_hash": "sha256:…" },
"codex": { "path": "codex.md", "content_hash": "sha256:…" }
}
}

If nothing changed, changed: false and no new version is created.

List the immutable compile history.

GET /api/v1/projects/{id}/rules/versions/{version}

Section titled “GET /api/v1/projects/{id}/rules/versions/{version}”

Fetch a specific compiled version, including compiled_outputs, knowledge_snapshot, context_snapshot, and content_hash.

When the SessionFS MCP server is connected, AI agents can read the canonical rules and their compiled projection directly.

ToolDescription
get_rulesReturns the canonical rules record and compilation config for the current repo
get_compiled_rulesReturns the compiled rule text for a requested tool, or for the current tool if safely inferable

Neither tool allows the agent to modify rules. Agent rule suggestions and auto-accept are explicitly deferred; humans control the canonical record in v0.9.9.

sfs rules compile refuses to overwrite my existing CLAUDE.md

That file predates SessionFS and has no managed marker. Either run sfs rules init to import it as the canonical seed, or rerun with --force if you’re sure you want to overwrite.

I ran sfs rules compile but the compiled file didn’t change

Compilation is deterministic and idempotent. If no input changed (static rules, knowledge claims, context sections), the output won’t change and no new version is created. This is by design.

sfs rules push returned 409 Conflict

Someone else updated the canonical rules on the server. Run sfs rules pull, re-apply your local changes, and push again.

Sessions aren’t capturing rules_hash

Session capture is best-effort. Check sfs daemon logs for warnings. If the project has no recognized rule file for the active tool, rules_hash will be null and rules_source will be none — that’s expected.

I don’t want my global ~/.claude/CLAUDE.md hashed into session records

Terminal window
export SFS_CAPTURE_GLOBAL_RULES=off

Project-scope artifacts continue to be captured.