feat: workspace rules in Claude Code and Codex sessions through the plugins

SA
1h ago
+2943-113

Summary

Builds on #1605 (merged). Rebased onto main: #1605 added on-disk "remember" requests, and they are combined with the stored proposals here (enqueue takes { id, forget }, requests_full applies only to shown cards, stored proposals are exempt from the one-day card limit). This brings workspace rules to Claude Code and Codex sessions through the altimate-code plugins. Plugin PRs: AltimateAI/altimate-claude-plugin#13 and AltimateAI/altimate-codex-plugin#7.

The plugins can't show proposal cards, and altimate-code doesn't run the host's conversation. Its hooks see the prompts, and after a turn the Stop check reads the host's transcript file. So the host's hooks call new learn subcommands:

CommandWhat it does
learn hook <event>Entry point for the plugins' SessionStart, UserPromptSubmit and Stop hooks. It reads the host's hook JSON on stdin, prints the hook output at session start only, and never fails the host.
learn rules [--session]The team rules after a pull (no push) that waits at most 3 seconds, or 8 when nothing has been pulled yet. They are given only while the credential signed in matches the one the last pull used. With --session, they are recorded as shown to that host session (Delivery.recordStart), so "corrected again", usage and the shown-rule edit path count it.
learn signal add --detectRecords the text only if correctionReason accepts it. "Remember this as a team rule: …" becomes a stored explicit proposal. No model call.
learn check --sessionRuleTrigger.afterTurn with persist: true, with the digest read from the Claude Code or Codex transcript (RulesHost.sourceFromTranscript). A Stop that arrives while the session's check is running marks it as having more work, and the check runs again before it releases its claim. Corrections left over from a session that ended are checked by a later Stop.
learn proposals [reply <id>]Lists and answers stored proposals with the same actions and failure codes as cards.

Stored proposals. When no client is attached to show cards, RuleProposal.ask keeps the proposal in rules.json (proposals) instead of making a card. Before, it was saved locally with nothing sent for review. A reply claims the proposal on disk first, then answers it through the same reply path, so the scope check, approve_anyway and the refused/failed handling don't change. Proposals are queued under their card id. A send already recorded as successful for the same wording is not queued again, and a closing answer is recorded on its claim, so only the same answer finishes it after a crash. If the server accepted a proposal but the answer was never recorded, the proposal can be sent again (the known no-submission-id limit). Limits: at most 20 proposals raised from corrections; past that, their corrections stay unchecked until some are answered. A "remember" the person asked for is kept past that limit, up to 100. A session proposal is dropped after 14 days; a "remember" waits until it's answered. Only the verified account and workspace a proposal was raised for can see and answer it.

Behavior change: a "remember … as a team rule" in a headless altimate-code run (no TUI or extension attached) is now stored for learn proposals instead of being saved to project training. Subagent sessions still save as before.

Gates. Team rules are given when workspaces are on, the project is linked and the person is signed in as the account they were pulled for, whatever the learning setting (as in an altimate-code session). Capturing corrections and drafting also need learning on. With no model configured for altimate-code, the Stop check fails and says so at the next start: it never signs up for one. The hook commands run without the instance bootstrap (Instance.provide), so a hook call starts no plugins, LSP, scheduler or background sync. Host session ids are claude:<id> / codex:<id>, and reflection treats them as external sessions.

Review

An independent review of the full diff found 3 MAJOR issues, all fixed here with tests:

  • prompt-submit and stop ignored a sign-out;
  • two Stop checks for one session could both draft (there is now an on-disk claim per session);
  • every hook call booted a full instance and started a sync the process then exited under.

Minor findings fixed: a claim TTL and same-process double replies, the recordStart lock fallback, the notice age limit, host-injected transcript rows, sanitized JSON output, no telemetry per hook call, and host-only --session.

Accepted: Stop can start before the prompt hook has recorded its correction. That correction is then checked on the next turn.

Consensus review (Claude, Codex, Gemini)

The review converged on NEEDS REVISION with 5 MAJOR findings. All are fixed in f2213dbb40, each with a test:

  • a correction made while a check was running could be dropped;
  • an edit could report success without being saved;
  • the session-start sync could be cut off in the middle of a push;
  • rules from an earlier account could be delivered, and a resumed session got its old snapshot back;
  • "remember this as a team rule", said directly to the host, was not proposed.

The fixable minor findings were fixed too: the stale learn check outcome, pinned rules first, a failed check reported at the next session start, dedupe on the full proposal identity, and the transcript_path file check. The rest are documented: the drafting model requirement, auto-reflection of host corrections, the Codex sandbox, and "remember" requests in CI.

Second consensus review (full feature)

The second review again converged on NEEDS REVISION, with 6 MAJOR findings. All are fixed in dfecbdafa5, each with a test:

  • Direct "remember": it was too eager and lossy. It now needs explicit team-rule wording, reads multi-line requests, and keeps a requested rule past the limit.
  • Leftover scheduling: one blocked session could starve the rest, failing checks were retried every turn, and later round outcomes were discarded.
  • Full queue: corrections that couldn't be stored were consumed. They now stay unchecked.
  • Session start: it had no overall deadline, and a pull that kept failing was waited out every time. It now ends within 10s (11s before the PR review round) and doesn't wait again for 5 minutes after a failed pull.
  • Subdirectory sessions: a link made for a parent folder wasn't found.
  • Credential gate: learn proposals and replies now apply it too, and a draft whose workspace changed mid-draft isn't stored.

The plugins now start no CLI in repositories with neither rules state nor a workspace link (claude-plugin 6e87e8f, codex-plugin adad91a).

Rejected: running the hook from the git root. A link can be for a folder inside the repository, and the CLI resolves the root itself.

PR review round (hkaltai, ralphstodomingo, bots)

Every open thread is answered inline. The changes, grouped by what they fix:

  • Consent: the host Stop check no longer auto-registers for Altimate Base. With no model it fails, and the next session start says so.
  • Gates: team rules are given with learning off, as in the TUI; capture and drafting still need learning on.
  • Cost:
    • no drafting call while 20 proposals are waiting;
    • "not stored" backs off like a failure;
    • the Stop-time sync honours the failed-pull back-off;
    • a stored proposal no longer holds back its session's later corrections.
  • Back-off: pull_failed_at is set only for a pull that ran and failed (or an unconfirmed link or account), and only holds while the same credential is signed in. Signed out is not a failure.
  • Locks: before a hook exits, closeLearnLocks aborts learn-lock waits that haven't started and waits up to 1.5s for held ones. Telemetry flush is capped at 1s. The start deadline is 10s.
  • Remember:
    • the rule is the request's first sentence;
    • a request read back at Stop isn't also a correction;
    • a request that can't be proposed is said at the next session start.
  • Attribution:
    • host corrections record turnStartedAt, so "corrected again" counts;
    • recordStart gives only rules it recorded, and re-dates only those it gave;
    • auto-reflect recovery skips host sessions.
  • Transcripts: Claude Code compaction summaries, interrupt markers and slash-command rows are skipped. The reader is split per host and uses bytesRead.
  • Stored proposals:
    • scope passed through explicit;
    • access rechecked inside the reply claim;
    • an answering marker that never acted no longer blocks;
    • a busy lock is reported rather than thrown;
    • a repeat keeps its evidence.
  • CLI:
    • rules --json keeps line breaks, and check --json is printable;
    • the answer hints name flags that work;
    • --detect needs a host session;
    • hook stdin is bounded.

Not done: running mayAnswer in parallel (at most 100 local checks). The undisclosed security finding is waiting on its details.

Test plan

  • bun run typecheck
  • bun test test/altimate/rules test/server/altimate-rules-routes.test.ts test/altimate/learn: 2217 pass at the second consensus round. After the PR review round: rules, learn, telemetry and cli suites 3831 pass (2 --time-budget timing tests also fail on main locally). host.test.ts (76) and host-cli.test.ts (8, real CLI) cover a crash between writes, a busy claim, a stale claim, two checks of one session, sign-out, a scope change, a failed or flagged reply, and the transcript formats.
  • Marker guard (analyze.ts --markers --strict)
  • End to end on a scratch project with altimate-backend/altimate-default:
    • learn hook stop recorded a correction that existed only in the transcript and stored "Name order timestamp columns ordered_at, not order_date.";
    • learn hook user-prompt-submit with "Remember this as a team rule: …" stored an explicit proposal.
  • Live run in Claude Code and Codex against a linked workspace, after release

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Claude Code and Codex support for receiving workspace rules, capturing corrections, and reviewing or replying to stored proposals.
    • “Remember” requests can be saved for later review when proposal cards aren’t available; stored proposals have capacity limits and correction-based expiry.
    • Added commands for listing, replying to, and reviewing proposals, running host hooks, and detecting correction signals in linked projects with learning enabled.
  • Documentation
    • Expanded guidance for host plugin setup, rule delivery, proposal workflows, and manual hook commands.
  • Bug Fixes
    • Improved rule delivery and correction handling across host sessions, including transcript processing and recovery from temporary failures.