feat: workspace rules — proposal card, owner review, approved rules in every session

Merged
SA
1d ago
+7906-342

Issue for this PR

Replaces #1438 (lesson sync), which is closed in favour of this redesign. Server side: AltimateAI/altimate-backend#7514 (/datamates/{id}/rules) and AltimateAI/altimate-frontend#3603 (the workspace Rules tab).

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

In a project linked to a workspace, what the person teaches the agent can become a team rule. They confirm it on a card, the workspace owner reviews it, and approved rules reach every teammate's sessions. This replaces #1438's background lesson sync, which sent staged lessons without asking.

Proposal card, built once in the session server (src/altimate/rules/proposal.ts, events.ts). It uses the same ask/reply shape as question: rule.proposal.asked goes out on /event, and the client answers with POST /rule-proposal/:id/reply. The TUI renders it now (plugin/tui/altimate/rules.tsx); the IDE extension can render the same event later with no logic of its own.

  • Enter sends the rule to the owner's queue; e edits it (re-linted); n ("Not a rule") suppresses the topic for 30 days.
  • The owner also sees a to approve at once, which becomes "approve anyway" when the rule weakens verification.
  • An unlinked project gets "Save for this project" instead.

When a card appears (trigger.ts), checked after each turn:

  • a topic corrected in 2 or more sessions with no rule covering it (from recurrence's unmatched topics);
  • a correction on the subject of a team rule the session was shown (an edit or retire card, with the current text struck through);
  • the person asks to remember a rule: training_save with kind: "rule" and project scope now shows the card instead of writing training.

One model call (the reflector, scoped to the triggering corrections) words the rule. If the drafter says the rule already covers it (HELPFUL), no card appears.

Rule or memory (classify.ts). A preference ("I prefer…") or a fact ("our warehouse is…") is memory, saved without review: training_save sends the agent to altimate_memory_write. When the wording says neither, the card asks "Save as: Memory · Team rule · Skip".

Sync (sync.ts, service.ts, store.ts):

  • Pull happens at session start (bounded wait), every 5 minutes, and on Sync now. It brings the approved rules, the settings, and review notices for the person's own proposals ("approved", or "rejected" with the reason). The notices show as TUI toasts.
  • Delivery: approved rules are mirrored into a read-only, git-ignored workspace-rules store (.altimate-code/learn/workspace-rules/; the name is reserved, and a store named team stays the person's own), so the existing delivery, recurrence and learn show treat them like any lesson. They reach the prompt's "Team rules" with their applies_to globs as path triggers. They stop being delivered when the project is unlinked, the person signs out, access is refused, or workspaces are turned off.
  • Outbox: proposals are queued offline-safe, per signed-in user (user:<id>), and sent on the next run under a cross-process push lock. Only an item the server refuses on its own leaves the queue; an account or workspace change drops it with a notice.
  • Usage: applied (sessions a rule was delivered in) and corrected again (recurrence after exposure) are reported back to the workspace.

Learning from sessions. In order: the learning kill switch, ALTIMATE_LEARN_CAPTURE, a learn.capture set by a config layer above project files (organization policy: managed config, MDM, the account's config, inline config, OPENCODE_CONFIG_DIR or the home config), the person's /rules choice (kept in git-ignored rules state), learn.capture in the project file, then the workspace's "Learn from sessions". startCapture takes force so the rules service can start capture on the workspace's say-so.

Also:

  • Server routes GET /rule-proposal, GET /altimate/rules/summary, GET /altimate/rules and POST /altimate/rules/sync.
  • The TUI gets /rules and a sidebar rules: line.
  • learn sync is new, and learn status shows a rules block with the last proposal check.
  • Behaviour changes for scripts: learn status text prints Learning from sessions: on|off (source) where it printed Capture:, and existing learn messages say "rule" where they said "lesson". The JSON output is unchanged apart from the added rules block. In a linked project with a client showing cards, training_save and altimate_memory_write can return metadata.action proposed, memory, redirect or error.
  • In a linked project, learn bootstrap and learn import-reviews send what they find for review instead of staging a local candidate.
  • api-client errors now carry the coded detail.
  • Docs: "Workspace rules" in configure/learn.md.

Not in this PR:

  • the one-time card offering to bootstrap from history in the background (learn bootstrap is the path today);
  • helpful/harmful usage counts (sent as 0);
  • the IDE extension's renderer.

Beyond the backend contract

This PR also adds, beyond syncing rules with the workspace:

  • the inline proposed-rule card in the TUI (ctrl+x p), and /rules (team rules, your proposals, Sync now, your own learning on/off);
  • a per-person learning setting (POST /altimate/rules/learning, stored in the git-ignored rules state);
  • learn bootstrap / learn import-reviews offering their staged rules to the owner's review, with a confirmation that names the workspace (--send-for-review for scripts);
  • training_save in a linked project proposing a team rule instead of saving project training (only when rules are enabled);
  • the rule-or-memory classifier for "remember …" requests.

Not in this PR (follow-up): proposals where no client renders cards — altimate-code run, ACP, and the Claude Code / Codex plugins (there the save tools save as before cards). The VS Code extension renders cards through AltimateAI/vscode-altimate-mcp-server#540.

How did you verify your code works?

  • bun run typecheck.
  • test/altimate/rules/rules.test.ts (129 tests): classification, applies_to, learning precedence, the team mirror, the summary line, card actions per project, edit lint and the verification flag, dismissal, sync (push, pull, usage, scope changes, locks), card replies and their failure paths, "remember" fallbacks and crash recovery, and the after-turn check.
  • Existing learn, carry-forward and server suites: 2438 pass. The httpapi-experimental / httpapi-mcp timeouts fail on a clean main too.
  • End to end, with the TUI in tmux against a local backend with #7514 and the Rules tab from #3603:
    • "remember this as a team rule" → card → sent → owner approves in the web → the next session receives it, and the agent cited it;
    • the same correction in 2 sessions → the card appears on its own with "seen in 2 sessions" and the quote;
    • editing the wording;
    • rejecting with a reason → a toast with the reason;
    • owner Approve now, and Approve anyway for "skip dbt tests…";
    • "I prefer CTEs" → saved to memory, no card;
    • usage counts visible in the web.

Cost side effects

  • Drafting: one model call per proposal check that finds a candidate, at most one per turn, and only after a correction (never for turns without one). It runs with the configured learn.model or the session's model.
  • HTTP: one request to the workspace every 5 minutes per open project, plus one per sent proposal and per usage batch. Sizes are bounded (≤50 proposals and ≤500 usage items per request).

Screenshots / recordings

"Remember this as a team rule" → card (owner view: Send, Approve now, Edit, Not a rule)
card

Corrected in 2 sessions → card on its own, with the evidence and the correction quote
recurrence

After e (edit wording)
edit

A rule that weakens verification: flagged, "Approve anyway"; the wording was unclear, so it also offers Memory
flagged

Rejection notice with the owner's reason
rejected

/rules after approval: team rules, your proposals, Sync now, learning on/off
menu

"I prefer CTEs" → saved to memory, no card
memory

The same rules in the workspace's Rules tab (#3603)
web

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Linked projects can sync workspace rules, submit proposals from corrections or explicit requests, and report rule usage.
    • Use /rules to browse team rules and proposals, manage personal learning settings, and sync. The learn sync command is also available.
    • Proposals can be edited, dismissed, or saved locally or as memories. Workspace owners can review and approve proposals.
    • The workspace sidebar displays rule status and the last sync time.
    • Imported or bootstrapped rules can be submitted for workspace review with consent.
  • Improvements

    • Project-level learning settings are supported. Capture and reflection are off by default; approved workspace rules are still delivered unless learning is disabled.
    • Learning status shows where its setting comes from, and documentation and CLI terminology now consistently refer to rules.