Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion brain/knowledge/ai-intelligence/ai-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A flow step type (the `run_agent` action of `@activepieces/piece-ai`) that runs
| `AGENT_BUILDER` | `conversation.modelName` | tier id |
| `FLOW_STEP` | `agent.published.modelName`, or the step's own `input.aiProviderModel.model` | concrete id |

Three things follow. `run_agent` has two modes: pointed at a saved agent (`agentId` set) the step sends no model and the agent row decides; inline, it sends `input.aiProviderModel.model`. An agent row carries two configs: agent-page chat reads `agent.draft`, a `run_agent` step reads `agent.published` (via `resolvePublishedAgent`), so the same agent can already run different models through different doors. The fast model crosses every door: `firstStepUsesFastModel` (`run-agent-turn.ts`) runs the first step of almost every turn on the fast tier, flow steps included, whatever the user picked. `execute-agent-run.ts` persists `config.tier.id` onto `conversation.modelName` when the job carried no `modelName`, so conversations already hold tier ids today (inline `run_agent`, eval runs, every `AGENT_BUILDER`), read back by `chat-tool-billing.ts`, `chat-analytics-sync.ts` and `pinnedModelOf`.
Three things follow. `run_agent` has two modes: pointed at a saved agent (`agentId` set) the step sends no model and the agent row decides; inline, it sends `input.aiProviderModel.model`. An agent row carries two configs: agent-page chat reads `agent.draft`, a `run_agent` step reads `agent.published` (via `resolvePublishedAgent`), so the same agent can already run different models through different doors. The fast model crosses every door: `firstStepUsesFastModel` (`run-agent-turn.ts`) runs the first step of almost every turn on the fast tier, flow steps included, whatever the user picked. `execute-agent-run.ts` persists `config.tier.id` onto `conversation.modelName` when the job carried no `modelName`, so conversations already hold tier ids today (inline `run_agent`, eval runs, every `AGENT_BUILDER`), read back by `chat-tool-billing.ts` and `chat-analytics-sync.ts`. **A persisted tier id is not a pin.** `pinnedModelOf` (`rpc/rpc-shared.ts`) ignores `conversation.modelName` for a conversation with an agent: that value is the resolved default, and once the console removes that tier it would reach a catalogue-less own-key provider as a literal model id. A saved agent's run model is the agent's own half (draft for AGENT, published for FLOW_STEP); only an agentless inline step reads `conversation.modelName`, which is the step's configured model.

### Tool types (AgentTool discriminated union)

Expand Down
19 changes: 12 additions & 7 deletions brain/knowledge/ai-intelligence/ai-providers.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions brain/knowledge/engineering/ci-pr-review-hygiene.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Enforcement is the **`Codeowners review` repository ruleset** (active on the def
- **Docs videos belong on `cdn.activepieces.com/videos/docs/`, never in the repo — and `docs/resources/` is the legacy path that suggests otherwise.** Every `<video>` already in the wiki points at that CDN prefix, but `docs/resources/` still holds ~43 MB of in-repo gifs from before the convention, so the tree itself gives mixed signals and a contributor reasonably copies the wrong one. [#15399](https://github.com/activepieces/activepieces/pull/15399) added a fresh `docs/videos/` folder with 155 MB of `.mp4` against an 880 MB `.git`, including one byte-identical duplicate. Nothing in CI catches it: `PR size` exempts unmatched areas and counts lines, not bytes, and a binary blob contributes zero lines. Review the *file list* of a docs PR, not just its diff. Once such a branch is pushed the blobs are already on the remote — dropping them at the tip only keeps them out of `main` if the PR is **squash**-merged and the branch deleted, so a merge commit ships them permanently. Because the blobs stay reachable until the branch is deleted, nobody has to re-request the assets from whoever recorded them — pull them straight back out of history with `git archive <commit> docs/videos | tar -x --strip-components=2 -C <dir>`, picking the commit *before* the removal, which is also how you get them under the post-rename filenames the pages already reference.
- **Mintlify-editor PRs arrive with `untitled-page*.mdx` at the docs root and `(1)`/`(2)`-suffixed media, wired into `docs.json` under those names.** The bot commits whatever the web editor autosaved, so pages land as `docs/untitled-page-5.mdx` rather than in the folder their nav group implies, and re-uploaded assets keep the browser's download-collision suffix (`data-flow-(1).mp4`, `01---insert-panel-(1.2x).mp4`). Mintlify's own link check passes — the names are internally consistent — so nothing flags it. Rename the files, fix the `docs.json` entries, then diff the *flattened nav tree* against `main` rather than reading the raw JSON diff: the editor also reindents, which buried a real change (the Enterprise Control `Security` group demoted to a child of `Guides`) inside 242 lines of whitespace churn on that PR.
- **After rewriting a lower branch of a stack, the `--onto` upstream you need is that branch's *old* tip, and `<branch>@{1}` is where to read it.** Every commit above a rewritten branch has to be replayed with `git rebase --onto <new lower tip> <old lower tip> <upper>`, and the old tip stops being reachable by name the moment the rebase finishes. Guessing a SHA from an earlier terminal scroll either fails outright with `invalid upstream` or, worse, succeeds against the wrong base and replays a handful of commits instead of all of them, which surfaces as a pile of conflicts the branch has with itself. `git rev-parse <branch>@{1}` reads it from the reflog every time. Re-run the whole `--onto` chain top-down after any lower-branch amend, and confirm afterwards that `git log --format=%s main..<top>` has no duplicate subjects.
- **When a stacked PR's parent is squash-merged, merge `main` into the child and retarget; do not rebase.** The parent's commits are on `main` by content but not by SHA, so `git merge origin/main` on the child sees both sides make the same change and auto-merges it; only the lines the child *rewrote* from the parent conflict, plus anything `main` changed in the same functions since (PR 2 of the model tiers work, #15825: three brain and docs pages where PR 2 had rewritten PR 1's sentences, and `agent-helpers.ts` where main's `resolveImageModelId` landed next to the surface-threaded fast-model helper). No history rewrite, a normal push, and the PR above it keeps its base untouched; a `--onto` rebase would replay every commit up the stack and force-push each. Two traps in the resolution. `git checkout --ours <file>` on a conflicted file takes the *whole* file from your side and silently discards main's auto-merged hunks in it, so resolve hunk by hunk and check `git diff origin/main -- <file>` afterwards shows only your lines. And `tsc` against the merged tree resolves `@activepieces/shared` through the built dist, which still holds the pre-merge exports: three phantom "has no exported member" errors vanished after `npx turbo run build --filter=@activepieces/server-utils`. Rebuild the core libs before believing a typecheck, then run the child's own tests.
- **Never commit a shared file onto a lower branch of a stack by copying it in from the stack top — you drag the upper branches' edits down with it.** Adding one gotcha bullet to a brain page while sitting on the top branch, then `git checkout <stash-or-top> -- <file>` after switching to the base branch, brings the *whole* file across, so every bullet the upper branches added to that page is silently duplicated onto the base. The commit stat is the tell: a one-line edit that reports six insertions is not a one-line edit. Switch branches first, re-apply the edit against **that branch's own copy** of the file, and re-check the stat before committing.
- **Cherry-picking a merged PR onto a release branch: the commit-msg hook rejects it, and `breaking-changes.mdx` conflicts pull in other people's entries.** GitHub writes the squash header server-side, so `fix(flows): … instead of reverting them (#14899)` never faced commitlint — replaying it locally trips the 100-character header limit and `git cherry-pick --continue` dies in husky with the working tree half-applied. Finish it with `git commit --no-verify -C <original-sha>` rather than rewording, so the release branch keeps the same subject as `main`. The `Unreleased` section of `docs/install/reference/breaking-changes.mdx` conflicts on almost every pick because it accumulates entries from unrelated `main` work: take only the section your own commit added. **Take it by its own heading, not by "everything after the marker" — the incoming side of that hunk usually runs on into `main`'s copy of an already-published `## X.Y.Z` section, and pasting the tail duplicates ~200 lines of published notes that no test, lint or typecheck will ever notice.** The reliable move is to rebuild the file: `git checkout origin/release/<version> -- docs/install/reference/breaking-changes.mdx`, then insert your entry under its own `## <new version>` heading above the previous release's, and confirm with `git diff --stat` that only your lines are added and `grep -c '^## '` that no version heading appears twice. Then run `bun install` before pushing — the release branch's `bun.lock` is usually stale against its own `package.json`s, and the pick drags those stale piece versions along until install corrects them; `git diff --stat origin/release/<version> -- bun.lock` should come back as just the packages your pick actually bumped. Expect the **PR size** gate to block it too: the picked commits drag their own upstream tests along (769/600 in `server/api` for two picks), and splitting a replay of already-reviewed work is the wrong answer — add `large-pr-ok`.
- **`api#test-ce` can exit 1 with every test passing — read the `Errors` line, not just `Tests`.** Vitest fails the run on *unhandled* errors too, so the summary reads `Test Files 92 passed`, `Tests 1076 passed`, `Errors 3 errors` and turbo reports `Failed: api#test-ce`. The recurring one is `TypeError: socket.destroySoon is not a function` at `Timeout.forceClose` in `@hono/node-server`, attributed to `test/integration/ce/mcp/mcp-platform-project-selection.test.ts` — a teardown race after the test already finished, not a failure of whatever you changed. Before chasing it, scroll to the `Unhandled Errors` block and check whether any test actually failed; if none did and the stack is in `forceClose`, re-run the job (`gh run rerun <id> --failed`) rather than editing code. Note the log section only has the whole `main` job, so `gh run view --job <id> --log-failed` buries it under thousands of lines of passing-test stderr — grep for `Unhandled Errors` or `Test Files`.
Expand Down
2 changes: 2 additions & 0 deletions brain/knowledge/flows-execution/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ A turn is kept alive / reclaimed by three separate mechanisms in `execute-agent-
- **The context budget ignores tool schemas and reserved `max_tokens`.** Anthropic/OpenRouter count both against the 200k window; `agent-compaction.ts` budgets neither. It trims history to `COMPACTION_THRESHOLD (0.7) × 200_000 = 140_000` and its fit check looks only at message chars, while `run-agent-turn.ts` sets `maxOutputTokens: tier.thinkingBudget + 32_000` → 52k reserved on premium, plus ~12k of tool schemas (62 tools, 41 via MCP). 140k + 12k + 52k = 204k, so a conversation that compacts to just under the threshold still 400s with "maximum context length is 200000 tokens" — and it gets retried ~6× (`streamText maxRetries: 3` × `MAX_STREAM_RETRIES`), burning ~20s per turn. `maxOutputTokens` is set at the `streamText` call level, so the full thinking budget stays reserved even on step one where `prepareStep` disables thinking and swaps in haiku-4.5 (real case: 148_628 text + 11_872 tool + 52_000 output = 212_500; dropping the unused 20k reservation alone would have fit). `ESTIMATED_TOKENS_PER_MESSAGE = 200` also sizes the recent window by message *count*, so a 12-message history holding ~235k tokens of uploaded documents summarized only 1 message. When budgeting, subtract the reserved output window and tool-schema size from `getMaxContextTokens`, and don't reserve `thinkingBudget` on a thinking-disabled step.
- **A write tool in `BUILD_ONLY_TOOL_NAMES` is only reachable if something flips the phase for it.** The denylist is the consistent home for anything that writes (`ap_create_flow`, `ap_create_table`, `ap_lock_and_publish` are all in it), but the only route out of `discovery` is `ap_set_phase`, whose description tells the model to switch when it starts *building an automation*. A tool for a subject with no build guide and no sibling build-only call, the agent-building tools being the case that found this, becomes invisible in any conversation that never builds a flow: `activeToolsForPhase` filters it out and the prompt names no tool, so the model cannot discover that it exists. Classifying by "does it write" is not enough; check what would actually flip the phase in a conversation about *that* subject. The four agent *write* tools (`ap_create_agent`, `ap_update_agent`, `ap_add_agent_tool`, `ap_remove_agent_tool`) were added to the set and then reverted for exactly this, with a test pinning the choice; `ap_list_agents` is a read and was never in it, so the group is five tools and only four were ever candidates.
- **Capability notes are built where `discoveryOnly` is not known, so a prompt can promise tools the worker has stripped.** `getAgentConfig` composes the system prompt in the api, while `discoveryOnly` rides on the job data; before Aug 2026 it never crossed that boundary. Meanwhile the worker strips image tools, email tools and the agent tools on such a run, so the notes claimed all three. Two of the three had been wrong since long before anyone noticed, because each note computed its own availability term. The flag now travels with the config request and the three notes read one shared `actingRun = !dryRun && !discoveryOnly`. Whenever a tool group is gated on a run mode in the worker, the note that advertises it has to be gated on the same term, in one place.
- **Tool output can't be trusted to waive a charge.** A user's own MCP server or `ap_run_code` controls its output's top-level keys, and they reach the persisted part unchanged. Chat billing only honours `billedAtCost` from `ap_web_search` and `ap_generate_image`; a new built-in tool that bills at cost must be added to `TOOLS_THAT_BILL_AT_COST` in `chatBilling`.
- **Credits are checked after every step, not just before the turn.** `runAgentTurn` asks the API (`agentHasCredits`) after each tool step, sending what the turn has used so far (`chatBilling.creditsForTurn`, the same rule end-of-turn billing uses), and stops once the remaining balance no longer covers it. Flat credits are only charged when the turn ends, so without that pending count the balance never moves mid-turn. The check fails open if the RPC errors.
- **Chat files belong to a conversation, not just a project.** Conversations are private to their user, so a project check is not enough. `saveAgentFile` and `persistAgentAttachments` stamp `conversationId` into the file metadata, and `readAgentFile` refuses a file whose stamp doesn't match. Files saved before the stamp existed can't be read back (image editing can't use them). Any new tool that reads chat files by a model-supplied ID must go through `readAgentFile`.
- Local dev needs `AP_DB_TYPE=POSTGRES` + Redis; refuses PGLite. **Prefer `AP_EDITION=cloud` over `ee` for chat work.** Cloud boots locally against plain Postgres and Redis with no Autumn, Stripe or license-key config (verified Aug 2026: API healthy, migrations applied, zero billing or license errors), and on Cloud `chatVisibility` returns `planChatEnabled || cloudRolloutOpen || userHasChatted`, so chat is simply **on** while the rollout cap is unfilled. On `ee` it is gated behind `plan.chatEnabled` and you have to get a plan onto the platform first. Note SMTP is usually unset locally, which makes the auth card open on the password form rather than the email-code step. Debug a run with `npm run chat:logs -- <conversationId> [runId]` (needs `LOG_FILE=true`/`AP_LOG_FILE=true` set when the turn ran — otherwise `.evlog/logs` is empty).
- **Chat was renamed to agent in code and DB, but only the storage half.** As of release 0.87.1 (`1823000000000-AddRenamedChatTableCompatViews`) `chat_conversation` → `agent_conversation` and `user_chat_memory` → `user_memory`, the server module moved `ee/chat/` → `ee/agent/` (entry point `agentModule`), the worker dir moved `jobs/ee/chat/` → `jobs/ee/agent/`, shared types moved `core/shared/.../ee/chat/` → `.../ee/agent/`, and `server/utils/src/chat-ai-utils.ts` → `agent-ai-utils.ts`. **The rename is not uniform, and the split is the thing to learn**: files describing chat as a *user-facing surface* deliberately kept their `chat-` names inside `ee/agent/` — `chat-visibility.ts`, `chat-rollout-service.ts`, `chat-rollout-user-entity.ts`, `chat-analytics-sync.ts`, `chat-tool-billing.ts`, `chat-usage-tracker.ts`, `chat-plan-grant.ts`. So a new chat-surface concern keeps the `chat-` prefix; a new stored entity takes `agent_`. The migration also leaves `CREATE OR REPLACE VIEW` compat views at both old table names, so raw SQL against `chat_conversation` still reads fine and will NOT tell you the rename happened — grep the entity, not the database.
Expand Down
1 change: 1 addition & 0 deletions brain/knowledge/memory.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- 2026-09-26 — PR 2 of the configurable tiers work (`feat/tier-resolution`, ENG-767) moved chat, agent and Run Agent model resolution onto the published tier file, with a required `surface` argument on the whole `agent-model-resolution` family and `surfaceOf({ source })` as the one source-to-list rule. Three calls made that day, recorded on the AI Providers page: a removed tier resolves to its surface default with a warning on every door; the `chat-tool-billing.ts` tier-label and FLOW_STEP-provider bug stays as it is and lives in ENG-772; the end-to-end re-point check runs on canary after merge with `AP_MODEL_TIERS_URL`, not locally.
- 2026-09-24 — `test-unit` (api unit tests) only joined the PR pipeline on 2026-09-20 (#15653). A branch whose last CI run predates that has never had its unit tests run in CI; expect first-time failures when it merges `main`, as #15550 did (a test mock disagreeing with the real code, not a merge problem).
- 2026-09-20 — Found while planning the AI tier work: managed AI piece steps under-billed for about 23 days, between #14942 (2026-08-24) and #15494 (2026-09-16). #14942 changed the AI piece's `provider` prop to store an object (`{ provider, configId }`), but `flow-run-ai-usage-extractor` read that field with a string-only helper, so it resolved to `'unknown'`, failed the `provider !== ACTIVEPIECES` check in `resolveAiCreditWeight`, and charged **1 credit** per step instead of the model's table weight. It hit the five non-agent AI steps only, and only those configured or re-saved inside that window — a step saved earlier still held a plain string and billed correctly. `run_agent` was never affected, because it stores `aiProviderModel` instead. Nothing logged an error, and every test in the file passed a string provider, so the suite was green the whole time. The code is gone (#15494 deleted the extractor and bills on real provider cost now), so there is nothing left to fix — but the cloud credit events for that window are still wrong, and nobody has reconciled them.
- 2026-09-20 — The live `ai/pricing.json` (version 1, published 2026-09-14) carried a leftover test tier `tier-4` / "New tier" on `anthropic/claude-opus-4.8`, created by clicking "add tier" in the pricing console and publishing. It was removed on 2026-09-24 by deleting the whole object, so the key is 404 until someone publishes again from the console; every server falls back to the bundled tiers and warns every five minutes until then. Next time, edit the row and publish; do not delete the object.
Expand Down
Loading
Loading