agentleFS
Sign inSign up

lobu

lobu-ai/lobu/AGENTS.md

AGENTS.md223 starsChanged 3 months ago
  • Reads credentials
  • Commits and pushes
<!-- Rules for agents (Claude, pi, codex); CLAUDE.md inlines this into every session. Keep it to facts an agent cannot derive and boundaries it must not cross. Reasoning and recipes go in docs/AGENT_PLAYBOOK.md, package specifics in the nearest package AGENTS.md. -->

## Repo map
- Bun workspace under `packages/*`; TS source in `src`, tests in `__tests__`.
- `core` types/utils · `plugin-api` plugin contracts · `plugin-host` plugin composition · `server` gateway + embedded runtime · `connector-worker` connector jobs and agent turns (V8 isolates) · `connectors` built-in connectors · `owletto` frontend submodule.
- Read the nearest package `AGENTS.md` before editing that package; grep `docs/GOTCHAS.md` when something looks inexplicable.

## Unrecoverable — never do these
- **Never write to `~/Code/lobu` directly.** Run `make task-setup NAME=<slug>` first, and never `git switch` in the main checkout — other sessions share it. For a pure submodule-pointer bump, `make bump SUBMODULE=packages/owletto` is the lightweight isolated-worktree exception; it also posts the required statuses.
- **Stage by explicit path** (`git add -- <paths>`, never `-A`). A commit is a snapshot, so a stale file copy left by earlier work in the same worktree silently reverts merged PRs. After commit/rebase, `git diff --name-only origin/main...HEAD` must equal your intended file list; extras = stop.
- **`events` is append-only.** Never `DELETE FROM events`; tombstone or supersede instead.
- **Never bulk-delete prod `organization` rows**, including zero-activity ones — empty-looking orgs are usually real signups. Surface them one at a time for confirmation.
- **Never resolve merge conflicts in the GitHub web UI** — it has silently dropped 1000+ lines of feature work. Rebase locally against `origin/main`; submodule-pointer branches `git merge` instead (`docs/GOTCHAS.md`).
- **Two writing agents must never share a worktree** — they overwrite each other's uncommitted work. Every subagent with Edit/Write/Bash gets its own through `make task-setup` (only task-setup does submodule pinning, `bun install`, `.env` copy, and port allocation); only read-only agents share the parent.
- **Never add a table or change DB design, API surface, or SDK surface** without surfacing the proposal and receiving confirmation first.
- **Never auto-submit an interactive browser draft, in any package.** Drafts are page-activated: persist the operation, notify normally, and let the extension activate the run only when the user visits a pending URL in a user-owned tab. Only scrape-owned scratch tabs may be opened and closed automatically. Server, connector, and Owletto code all obey this (`packages/server/AGENTS.md`).
- **Tenant/example state stays with its owner.** Entity/relationship schemas, event kinds, Automations, prompts, agents, device pins, task IDs, and policies for one org, customer, project, or example belong in that example/config — never shared core, server, worker, SDK, or migrations. Before editing shared packages for an example, state the reusable platform invariant; if there is none, stop. Design-lock a genuinely missing public API/DB/cross-runtime contract as a separate PR.
- **Shared runtime never special-cases user entity keys.** Only reserved `$...` entity types are platform-owned. All other semantics come from the entity schema or an explicit generic contract; an existing literal such as `person` or `company` is not precedent.
- **Connector logic stays connector-owned.** Put it in the connector implementation or an explicitly named connector adapter/auth/webhook/migration module, registered through a generic contract. Never add connector-slug branches to otherwise generic runtime modules; extend the connector contract when the generic path lacks a capability.
- **Never commit live tenant identifiers or personal machine state.** No real org UUIDs, entity/Automation/device/connection IDs, usernames, personal absolute paths, or task numbers in shipping code, shared migrations, or script defaults. Tenant state belongs in its example/config; tests use clearly synthetic values; operational scripts require explicit inputs.
- **Generated and published artifacts must be portable.** Never embed a developer's home, checkout, or worktree directory in catalogs, bundles, metadata, lockfiles, or package contents. Resolve runtime paths from installed assets or explicit user configuration; use synthetic paths in fixtures. Enforce this on generated/packed bytes with portability tests, including foreign build-machine paths and encoded file URLs — source grep alone cannot detect build-time leaks.

## Facts you cannot derive
- **`events.id` is a stored-version id, not stable source identity.** A resync supersedes the row and mints a new id for the same source item, so cross-sync identity and dedupe must key on the connector's `origin_id` (scoped to its connection/source) or an explicit domain key.
- **Request paths never aggregate history.** No `GROUP BY`, `DISTINCT ON`, per-row `regexp_*`, or leading-wildcard `LIKE` over `events`, `agent_transcript_snapshot`, `session_calls`, … on a user-facing read path: history grows, the answer doesn't. Materialize at write time, read back by index, backfill in a migration. Bounded config tables (`connections`, `automations`, `agent_users`) are fine.
- **Shared state must be Postgres-mediated.** An in-memory Map or singleton is invisible to other replicas — correct in dev, broken in prod.
- **Workers never receive real credentials** — placeholders or proxied access only. The exceptions are device-pinned connectors and short-lived provider-derived leases (`packages/server/AGENTS.md`); a durable stored credential is never one.
- **Automation is the product, API, and storage vocabulary.** Use it consistently in public contracts and internal identifiers; `make pre-pr`'s exposed-surface naming gate rejects retired product and engine terminology.
- **Per-connection approval override is `connection.config.action_modes`** (`'disabled' | 'approval' | 'auto'`), consulted by `resolveActionMode` BEFORE the connector's `requiresApproval` default. Writing, changing, or removing this map — including via connector `default_connection_config` — requires a human web session; agents/tokens may only round-trip it unchanged. A run parked at `approval_status='pending'` does not mean "no auto path exists" — grep `action_modes` / `operations/action-modes.ts`, not `auto_approve`. Connector-run approvals (`resolve_approval` / `approve_batch`) are human-only by design: the server rejects agent/token approval of operation runs.
- **A local install can never finish a connector OAuth flow** — `<base>/connect/oauth/callback` is not a registered redirect for any localhost port, so the handshake 400s. The grant lives in a cloud (public) org via `connect_managed`; the local connection opts in with `config.managedBy = { org, connectionSlug }` and fetches a short-lived token at runtime. Never reach for a provider console edit or a hand-minted token first (`docs/managed-auth.md`).
- **`make review` is the semantic review gate, not CI.** It runs no typecheck, knip, or tests; a verdict or safe-class skip is not evidence CI will pass.
- **GitHub CI is the canonical gate** — free on this public repo, full graph in ~5–7 min per PR. `make pre-pr` (local fast gates: typecheck, knip, lint, naming) catches the cheap misses before push; `make review` verifies CI is green for HEAD. `make pr-full` (Daytona ephemeral sandbox, else local) is optional tooling, not part of the required loop.
- Default to static `import`; a new production dynamic import needs measured justification plus a call-site rationale comment. Tests may import dynamically after mocks; two Node-version gates are grandfathered (playbook).

## Ship a change
1. `make task-setup NAME=<slug>` → work in `.claude/worktrees/<slug>/`.
2. Reproduce red → fix → prove green, and paste both outputs in the PR. Cannot reproduce = report the dead end; never ship a speculative fix.
3. Iterate on focused tests: `bun test <path>` for bun:test suites, or `cd packages/server && bun run test -- run <path>` for Vitest suites. Never use `bunx vitest` for server suites. `make pre-pr` catches the cheap common misses; the full graph runs on GitHub CI for the PR.
4. `make review-fix` on the settled diff, then re-read what it touched. Safe-class changes self-skip before selecting an LLM. Otherwise it runs BEFORE the first `make review` — never iterate `make review` as a find-fix loop, since each posted round costs a review + CI cycle.
5. Stage by explicit path, then run `make pre-pr` (build + typecheck + knip + biome + naming) BEFORE committing and pushing — the pre-commit hook runs biome in FAIL mode (no auto-fix) plus typecheck, so if you skip the local gates, CI's format/lint check is the first thing that catches a slip (it has, more than once). GitHub CI is the gate: `make review` fails unless CI is green for HEAD (override: `REVIEW_ALLOW_LOCAL_BUILD=1`).
6. Commit, then confirm `git diff --name-only origin/main...HEAD` is exactly your intended file list.
7. `git push -u origin <branch>` → `gh pr create` (fill `.github/pull_request_template.md`; conventional-commit title).
8. `make review` **once** on the settled HEAD; it posts the required `pi-review` status. Narrow path/content-gated safe classes skip the cross-harness review; this includes pure `packages/owletto` pointer bumps and exact `model:` literal swaps, but not mixed runtime and source changes. The submodule's own repo owns its content review.
9. `make ui-review`. Non-Owletto changes and complete, forward-only Owletto pointer diffs confined to unhosted trees (`deploy/`, `apps/chrome/`, `apps/mac/`, `scripts/`) pass as not applicable; other pointer changes need exact hosted proof (`ARTIFACT=<url>`; see the playbook).
10. `make land N=<n>` once pushed — it waits, verifies the full required list, and squash-merges. Never `--admin` past a check that has not reported. Lobu's required `integration` fan-in is absent from `gh pr checks` until it starts, not pending, so diff against the branch-protection list (playbook).
11. Prod-visible surface? Verify live after rollout: gate on the **squash commit**, not your branch head, keeping the argument order `git merge-base --is-ancestor "$MERGE_SHA" "$DEPLOYED_SHA"`. Record the result.

## How to work
- Do only what was asked. Delete ephemeral files you create; no new `*.md` unless asked.
- Run mandated gates automatically; never ask permission to run one.
- **Evidence, not assertion.** Never report done off a green typecheck — boot it, exercise every branch you touched, and clean up test data; if "compiles" is all you ran, say so. Show `git status` plus `git diff --stat <base>...HEAD`, including for a dispatched agent's work.
- **Absence needs `origin/main`:** `git fetch -q origin && git grep <pattern> origin/main`. A working-tree grep proves nothing, and the fetch is load-bearing.
- **Scope-leak checks are class-wide.** For tenant/example, entity, or connector work, search committed `origin/main` for the related entity keys, connector slugs, tenant names, IDs, and absolute paths; then verify the final diff contains only the owning surface. Existing violations are not precedent.
- Deleting code needs structural evidence — a dangling import, a completed migration, a superseded implementation. Low prod usage is not evidence, and docs and help text are load-bearing.
- Fix the class, not the instance: on the first hit, grep every other occurrence and fix in one pass. A diff-scoped reviewer only ever finds the next one.
- **Classify on structure; prose is the last resort.** An HTTP status, exit code, stop reason or typed enum is the primitive — a message string is a rendering of one, and matching it needs a new alternate per vendor per phrasing and fails SILENTLY when it misses. Derive the typed code once, at the boundary that still holds the structure (`gateway/proxy/provider-health-status.ts` maps status → `AgentErrorCode`), and pass the code onward; never re-derive the same distinction downstream. Where text genuinely arrives with no structure attached — an agent CLI's crash tail relayed through a worker — the pattern lives in the ONE catalog every layer consumes (`packages/core/src/classify-error.ts`), never in a second copy next to its consumer. Three drifting copies of the quota vocabulary is what charged 40% of prod's `agent_error` runs to the agent (#3619).
- **A shared key is derived by one function, never read off whatever carried it.** When two sides must meet on an identifier — a marker, a queue singleton, a cache key — both call the same generator from the same inputs (`generateDeploymentName`, `turnMarkerDeploymentFromClaims`). A name lifted off a token claim, URL segment or response body is a DIFFERENT namespace that merely looks right: the lookup matches zero rows, and zero rows is usually indistinguishable from the legitimate empty case, so the feature ships inert. A test that builds both sides from one invented literal cannot catch this — drive each side through the generator production uses.
- When a run/operation hits a gate (approval, auth, resolution), **trace the existing decision path** (handler → policy → config readers, e.g. `resolveActionMode`) and grep the product's own vocabulary BEFORE proposing new surface — the mechanism usually exists under a name you have not tried yet.
- One branch = one concern. Never `git stash`; use WIP commits.
- Prefer `bun`, never npm/yarn/pnpm. Before adding an env var, grep for the one already read, and do not rename existing vars unasked.
- Block only on irreversible or destructive actions and decisions genuinely the user's; otherwise take the recommended option and flag it in your summary.
- **Compose before you extend.** Before adding SDK/API/DB surface, prove the need cannot be met by composing existing platform features (Automations, suggestions, approvals, policies, existing tools) — and dogfood that composition first. New surface is a last resort, not a first draft: it needs the Unrecoverables confirmation *plus* that proof.
- **Batch independent tool calls into one message.** Each call re-reads the current context. Independent greps, a diff plus a file list, a status check plus a log read: send them together. Only genuinely dependent calls go serial.
- **`make ctx` instead of the git/gh status family.** One call prints branch, working tree, changed-vs-base file list, commits, submodule pointer, and PR + required-check gaps; do not rebuild it by hand.
- **`make land [N=<pr>]` to merge.** One blocking call: waits for CI, diffs the reported checks against the branch-protection required list, and refuses while anything required has not reported — the `--admin` footgun in step 10. `CHECK_ONLY=1` waits and reports without merging.
- **`make sandbox` for a full stack off this Mac.** One remote sandbox per worktree — server, Vite, its own embedded Postgres — on a preview URL, so worktrees never contend for ports or a database. `make sandbox-run CMD='<cmd>'` offloads a build or suite; `make sandbox-stop` frees the org's running-memory quota (10GB total, ~2 sandboxes at once) while keeping the disk and database.
- Keep working context under ~150k. Start a fresh session at each PR boundary and push open-ended search into read-only subagents rather than growing one session until it compacts.
- Never poll in the foreground — run long waits in the background and act on the notification. Poll delegated CLIs at most once every 4.5 minutes unless they finish or request input.
- Prefer DOM reads over screenshots, the top context-bloat source. The paired Owletto extension drives the user's real logged-in browser; CDP is not required (`docs/BROWSER_TESTING.md`).
- Slack link pasted → run `scripts/slack-thread-viewer.js "<link>"` first.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.