This is the entry point for AI guidance in Apache Fory. Read this file first, then load only the .agents/*.md files that match the runtimes or areas you touch.
.agents/README.md: routing table for selective loading..agents/repo-reference.md: repo layout, architecture, compiler notes, and key directories..agents/docs-and-formatting.md: documentation, specification, and markdown rules..agents/ci-and-pr.md: code review workflow, CI triage, PR expectations, and commit conventions..agents/testing/integration-tests.md:integration_tests/prerequisites, regeneration rules, and commands.docs/object-serialization/security.md: user-facing security guidance for binary object serialization.docs/json/security.md: user-facing security guidance for Fory JSON.docs/object-serialization/deserialization-security-model.md: implementation boundaries for untrusted deserialization classification..agents/languages/java.md.agents/languages/csharp.md.agents/languages/cpp.md.agents/languages/python.md.agents/languages/go.md.agents/languages/rust.md.agents/languages/swift.md.agents/languages/javascript.md.agents/languages/dart.md.agents/languages/kotlin.md.agents/languages/scala.md- For protocol or xlang changes, load the relevant language files plus
.agents/docs-and-formatting.mdand.agents/testing/integration-tests.md.
- Preserve architecture. Do not introduce new layers, parallel flows, or public APIs unless explicitly requested; prefer local repair in the existing owner over shared-infra expansion, and stop if a fix conflicts with an ADR, spec, or invariant.
- Do not change an existing
RefReader/RefWriterarchitecture or API to support compatible skip. Compatible skip must not add alternate reference slots or tables, alternate reference lookup or publication methods, or forwarding APIs in read/write contexts, builders, serializers, or generated-code plumbing. Keep ordinary reference publication and lookup unchanged and resolve the case in the existing compatible generated owner. For an authorized removed-field read of an unregistered Struct, the empty object created by the skip reader is that path's final owner: publish that same object forRefValue, consume the Struct fields, and let laterRefFlagvalues resolve to it. This preserves reference numbering and identity without registering the Struct; an independent dynamic root still requires normal registration. Do not add parallel reference state, a sentinel, a rejection, or a common-path branch for this case. - Respect ownership. Keep logic, state, and helpers in their natural owner, and do not move serializer-local, context-local, runtime-type-local, or protocol-local problems into global utilities.
- Check the spec before implementation. For wire behavior and xlang mapping, use the specs as the source of truth and never copy one runtime's bug into another runtime just to make tests pass.
- Do not make assumptions about runtime behavior, ownership, registration, metadata construction, protocol semantics, or test coverage. Read the current code, owning docs/specs, and relevant tests before making a design judgment or implementation decision. If the evidence is incomplete, inspect more or state the uncertainty explicitly instead of filling gaps from memory or analogy with another runtime.
- For untrusted deserialization, read
docs/object-serialization/deserialization-security-model.mdbefore changing allocation, stream filling, skip, reference, metadata, or policy validation behavior. Variable-length deserialization must not allocate or reserve backing/output capacity from attacker-declared lengths or counts before the byte owner has proven proportional readable bytes withcheckReadableBytesor the runtime equivalent. Root graph memory reservation is accounting only and may happen before that byte check, but it must not replace the byte check. - Malformed input must surface as a controlled root-operation error and still run root cleanup, but the exact exception type, error code, message, detection layer, and detection point are not contracts unless a public API or specification explicitly says otherwise. An existing bounded downstream buffer, type, reference, depth, or serializer error is sufficient. Do not add hot-path branches, helper APIs, allocations, or generated-code expansion solely to make an error earlier, more specific, or more uniform, and do not write tests that force such error normalization.
- Never add a reader-side check solely to produce a more precise malformed-input error. Retain or add a check only when the unchecked path has a concrete consequence such as a crash, panic, undefined behavior, out-of-bounds access, disproportionate work, no progress, persistent state pollution, or a real type or policy violation. If a necessary check is reachable from a hot or generated path, keep the success path to a primitive branch and move exception creation, message formatting, and other failure work into a cold no-inline helper when the language supports it. A bounds-safe downstream operation that already raises a controlled root error is sufficient; do not duplicate it for error precision.
- Before reporting or fixing a robustness finding, prove that the current path causes at least one concrete consequence: crash, panic, undefined behavior, or out-of-bounds access; disproportionate allocation, CPU work, or stream growth; a no-progress loop; persistent state, reference-table, or cache pollution; later-root corruption or a failed-root cleanup leak; or a concrete type, registration, callable, or deserialization-policy violation. Protocol strictness alone is out of scope. Do not change code merely because a malformed or noncanonical flag, enum value, marker, length form, or reserved value is accepted, rejected late, decoded differently, or produces a less precise error.
- Arbitrary-precision binary Decimal codecs accept only scales in
[-10_000, 10_000]and an absolute unscaled magnitude of at most10_000binary bytes. The Java standaloneBigIntegerserializer uses the same magnitude limit. This is a value-range rule, not a wire-format change:magnitudemeans the canonical unsigned bytes of the absolute value, not a signed two's-complement prefix, protocol headers, decimal digits, or the Fory JSON10_000-character limit. Readers must validate the range before allocating magnitude storage, constructing arbitrary-precision values, or expanding scale, while retaining the existing readable-byte, negative-length, overflow, and canonical checks. Writers must validate symmetrically before emitting any part of the value and before materializing an oversized magnitude. Compare scale directly with both bounds; do not useabs(scale), which can overflow for the minimum integer. Fixed-range Decimal carriers keep their stricter native ranges and reject oversized magnitudes before copying or construction. Compatible scalar conversion keeps its independent256-digit and scale/output-expansion limits. - Root deserialization graph memory budgets are approximate gates for materialized graph owners,
not exact heap accounting, input byte accounting, or raw element counts.
maxGraphMemoryBytesdefaults to fixed128 MiB; positive values override the default; explicit non-positive values are invalid and must be rejected at config/Fory creation. Do not add a disabled-budget sentinel path, derive this budget from root input size, or split known-length and stream root behavior. Read context/read state owns only raw byte reservation withreserveGraphMemory(bytes); it must not expose counted arithmetic helpers or collection, map, array, struct, or object semantic reservation APIs. Do not add any non-memory-budget read-context/read-state API for this feature, including ref-publication controls, temporary-owner controls, serializer-owner controls, conversion helpers, or APIs that encode what kind of value is being materialized. Root facades may set/reset the per-operation budget, but they must not pre-reserve root type, root self bytes, or root value storage. Because the budget is fixed per root, read state must not mirror the configured max into a second active-limit field; use the existing config or one configured max field plus the mutable remaining budget. Concrete serializers and generated serializers own counted formulas, overflow checks, allocation-owner decisions, and reference publication timing for their allocation path. Reserve self storage exactly once at the owner that stores or allocates the value: reference/object runtimes reserve parent owner self cost plus reference storage and every referenced heap owner reserves its own shallow self cost when materialized; inline/value runtimes reserve value storage only in the holder or materializer that actually owns that storage, such as collection, map, set, array, smart-pointer, box, dynamic boxing, or reference-object field storage owners. Value serializers do not reserve their own self storage, including root and generated struct/product read paths. Collection, map, set, and reference-array owners reserve nonzero shallow self cost only when that runtime materializes an independent reference/backing owner, plus backing/reference/inline storage. Struct, record, POJO, compatible, generated, and dynamic object owners reserve a nonzero shallow self cost plus shallow field storage only in reference-object runtimes or dynamic/boxed materialization paths; inline/value struct serializers do not self-charge. Reference fields use 4 bytes when reference size is not cheap or reliable to query; primitive/value fields use their storage width. Parents do not recursively include child object, collection, map, string, binary, or primitive dense-array contents. Skip enum/union as separate owners and skip dedicated string, binary, primitive scalar, primitive array, and primitive dense-array leaf owners unless a runtime-specific owner rule includes them. Java Fory core primitive arrays reserve their array header plus primitive storage once from the validated logical length; primitive-list serializers additionally reserve the list's shallow owner. Java Fory JSON primitive arrays decoded from JSON arrays reserve their array header plus primitive storage in 1024-element batches and at the tail; abyte[]handled by a JSON binary or Base64 codec remains a binary leaf. Leaf values skipped by the graph budget must remain gated by byte-availability checks on the unread input; if remaining bytes are insufficient, the leaf value must not be read or created. Actual process memory can be higher than the graph budget. Do not guess allocator, bucket-table, node, debug, or map-entry overhead unless it is a documented lower-bound owner allocation. Do not add dynamic stream bytes-read accounting or nested hot-path cleanup just for this budget. - Count-driven collection and map reads use one root-shared unbacked-container item allowance.
The public language-equivalent
maxUnbackedContainerItemsdefault is8192; values are non-negative and zero is strict. For an exact repeated body that is compile-time or generated proven to consume at least one byte, retain the direct loop and proportional readable-count gate with no allowance access, cursor snapshot, or periodic branch. Before count-derived allocation for an uncertain body, require readable bytes formax(0, count - remaining allowance)without replacing graph-memory accounting. In the existing single collection loop, settle actual body progress every 1024 completed elements and at the tail; maps settle only at their existing chunk boundaries, with one entry counted as one item. Compatible skip shares the same root allowance but invents no graph owner. Root reset alone clears the allowance. Writers must continue to emit valid compact empty bodies; do not reject, pad, split, or change wire encoding for this reader policy. Do not add an outer batching loop, probe reads,isEmptyType, or RefReader/RefWriter APIs. - Name the serializer or codec progress capability after the operation it proves: use each
language's idiomatic form of
readDataAlwaysAdvances, with no body-named compatibility alias. This fact describes only the serializer-ownedreadDataoperation. A field, collection element, or map entry may instead advance because of ref, null, or type envelopes; name those derived factsfieldReadAlwaysAdvances,elementReadAlwaysAdvances, orentryReadAlwaysAdvancesrather than conflating them withreadData. - For remote TypeDef/TypeMeta reads, the checked metadata cache is the only owner of remote "already validated" state. Cache hit means the header was previously parsed, body/hash-validated, policy-checked, and published by that cache, so the hot path must skip the body and use cached metadata without extra validation, hashing, limit checks, exact-local checks, allocation, or policy work. A known expected local TypeDef/TypeMeta header/hash match is a local-schema hit, not a remote cache miss: it may skip the body and use the local TypeInfo/TypeMeta without schema-version counting or cache publish. Cache miss is the only path that parses and validates non-local metadata, enforces limits, performs exact-local byte comparison when needed, and publishes remote metadata to the cache. Do not add nullable accepted-header fields, sentinel headers, per-TypeInfo markers, pending metadata state, parallel header-low/header-high slots, or parallel acceptance state for this decision. If a runtime needs a metadata hit hint, cache the concrete checked metadata owner object, such as the TypeInfo, TypeDef, or TypeMeta used by that runtime, and compare its validated header identity directly.
- When a user corrects a non-obvious invariant, encode it in the nearest source comment before continuing, and also update
AGENTS.md,.agents/**, docs, or specs when the rule is reusable beyond one file. Do not rely only on chat history, task notes, commit messages, or benchmark logs for corrections that protect security, protocol behavior, ownership, naming, or hot-path performance. - Reject semantic hacks. Do not bypass broken semantics by deleting cases, simplifying callers, adding coercion hooks, or using workaround fallbacks; fix the underlying bug and prove it with focused tests.
- Protect hot paths. Avoid per-call allocations, callback objects, result tuples or records, unnecessary runtime branches, and wrapper-class substitutions in hot codec/runtime paths; prefer conditional imports and allocation-free concrete implementations where they fit the language.
- Decoder depth and the generic-type stack paired with that depth use root-operation failure cleanup. Nested decoders decrement depth and pop generic types only after successful child reads; do not add nested
try/finallyto restore them after exceptions. The root operation'sfinally/reset must clear both decoder depth and the generic-type stack. - Keep public APIs minimal. Public APIs must match user ownership and mental model, not internal implementation details; generated flows stay type-owned, while custom serializer registration stays explicit.
- A Fory instance may register types or serializers only before its first root serialization or deserialization operation. Starting either operation permanently freezes that instance's registry, including when the operation fails. Every later registration attempt must fail before mutating resolver maps, generated descriptors, metadata, serializers, or caches. Do not support post-use registration through cache invalidation, descriptor refresh, serializer rebinding, metadata rebuilding, or other late-registration machinery. Registration-order finalization before the first root operation remains registration-owned and must not create a runtime invalidation path.
- Use semantic naming only. Name things after protocol or domain concepts, not history, runtime origin, or workaround style; avoid vague names such as
Internal,java_style_*,Runtime,Session,Plan,Payload, orBindingwhen they do not name the real concept. Keep class, method, function, and variable names concise; do not encode the whole scenario or implementation history into one identifier. Never name a class or method with aPlansuffix; use the real domain concept instead. For Fory codec/read APIs, do not use genericpayloadnaming; name the exact owner and data shape, such as bytes, body, frame, field, string, list, map, compressed bytes, or primitive-array encoding. - Keep one implementation path. Do not keep parallel helpers, serializers, harnesses, wrappers, or registration flows for the same concept; extend the existing owner path instead of inventing another one.
- Follow current scope exactly. The latest explicit user instruction overrides earlier plans, and when scope narrows, remove leaked out-of-scope edits immediately.
- Preserve user corrections. When a user corrects code behavior, ownership, invariants, or review feedback in a way that should prevent repeat mistakes, encode the corrected rule where future agents will see it: prefer the nearest source comment for non-obvious code invariants, or the owning docs/spec for user-visible or protocol behavior. If the correction changes API usage, defaults, generated output, tests, or cross-runtime behavior, update the matching docs, examples, or source comments in the same task so future agents do not repeat the violation. Keep the note concise, English-only, and avoid comments that merely restate obvious code.
- Verification is required. Match validation to the real ownership path: compile, tests, xlang, native-image, non-VM compile, benchmarks, and remote CI as applicable; reasoning alone is never enough.
- Finish the whole surface. A feature or behavior change is incomplete until code, tests, docs, exports, examples, and build wiring agree, unless the user explicitly defers part of that surface.
- Keep task boundaries strict. Review tasks do not edit code, analysis-only tasks do not silently turn into implementation, and active-branch fixes must land in the active branch/workspace.
- For non-trivial multi-step tasks, write the plan and progress into the canonical durable task file (use a matched skill/workflow file if it provides one, otherwise use a file under
tasks/) and read that file after compaction before continuing.
- Record all core design and decisions in the owning docs when they belong there, especially under
docs/object-serialization/**, the relevant capability directory, ordocs/specification/**. - Do not allow implementation drift from the design document.
- Do not compromise design decisions to make implementation easier.
- Do not leave workaround code behind.
- All code must have a clean owner model; the wrong owner model or abstraction is unacceptable.
- Do not leave ugly or temporary code behind.
- Do not leave legacy, dead, useless, or stale code, tests, or docs behind.
- Do not leave avoidable technical debt behind.
- Do not preserve legacy, dead, or useless code, tests, or docs unless the user explicitly requests it.
- Ignore internal API compatibility unless the user explicitly requests it. Do not keep shims, wrappers, or transitional paths only to preserve internal call sites.
- Performance is the top priority. Do not introduce regressions without explicit justification.
- "Refactor" means changing structure, ownership, naming, or API shape without changing behavior, wire format, or implementation strategy unless the user explicitly asks for those changes.
- Do not make design tradeoffs the user did not request. If a refactor appears to require a behavior, logic, protocol, or performance tradeoff, stop and ask.
- Treat existing low-level or optimized code as deliberate by default. During a refactor, preserve the current implementation strategy unless the user explicitly asks to redesign or optimize it.
- Do not replace existing C, C++, Cython, unsafe, or other low-level optimized paths with simpler high-level implementations just to make a refactor easier.
- If a refactor accidentally changes logic or implementation strategy, revert that part and re-implement the refactor around the existing logic.
- Use English only in code, comments, and documentation.
- Do not use emoji in documentation, including headings, feature lists, status tables, callouts, or READMEs. Use plain words such as "Supported" or "Unsupported" instead.
- After editing Markdown files outside
tasks/, runprettier --write <file>on each changed Markdown file before finishing. Do not format Markdown undertasks/. - User guide docs must explain user-visible behavior, commands, and examples. Do not add implementation details, internal ownership rationale, build flags, or type-id-space caveats unless they directly clarify a confusion users can act on. Translate internal owner-model details into concrete user actions, and avoid phrases such as "serializer-owned capability" or "registration alone does not..." in user-facing docs.
- Add comments only when behavior is hard to understand or an algorithm is non-obvious.
- Do not remove existing code comments unless they are stale, misleading, redundant, or no longer necessary after the change.
- Only add tests that verify internal behaviors or fix specific bugs; do not create unnecessary tests unless requested.
- Do not add cleanup-sentinel tests that only pin deleted APIs or removed fields.
- Tests must exercise the actual code you wrote or changed. Do not write tests that pass by exercising a pre-existing code path that produces similar-looking results. Before writing a test, identify the exact new code path (annotation, codegen output, new API) and verify the test would fail if that code path were removed. When the change involves codegen or annotations, the test must use those annotations on real structs, run through the codegen pipeline, and verify the generated output drives the expected runtime behavior.
- Keep test method names concise. Name the behavior under test without encoding the whole scenario or expected result in the method name.
- When reading code, skip files not tracked by git by default unless you generated them yourself or the task explicitly requires them.
- Maintain cross-language consistency while respecting language-specific idioms.
- Keep one active ownership path per concept. Do not leave duplicate serializers, resolvers, helpers, or registration paths for the same type family unless the split is deliberate and documented.
- Name new or touched APIs, helpers, and tests after protocol concepts, data-model semantics, or user-visible behavior; avoid names based on runtime origin, bug history, storage details, or temporary workarounds.
- For public cross-runtime or protocol features, update runtime support, compiler/codegen wiring, public exports, docs, specs/type mappings, integration fixtures, and tests together unless the user explicitly defers part of that scope.
- Do not introduce checked exceptions in new code or new APIs.
- Do not use
ThreadLocalor other ambient runtime-context patterns in Java runtime code.WriteContext,ReadContext, andCopyContextstate must stay explicit, generated serializers must not retain context fields, andForymust stay a root-operation facade rather than accumulating serializer/runtime convenience state. - When a serializer class and constructor shape are known at the call site, prefer direct constructor lambdas or direct instantiation over reflective
Serializers.newSerializer(...). Keep reflection for dynamic or general construction paths only. - For GraalVM, use
fory codegento generate serializers when building native images. Do not use GraalVM reflection configuration except for JDKproxy. - In Java native mode (
xlang=false), onlyTypes.BOOLthroughTypes.STRINGshare type IDs with xlang mode (xlang=true). Other native-mode type IDs differ. - Keep class registration enabled unless explicitly requested otherwise.
- Prefer schema-consistent mode unless compatibility work requires something else.
- When debugging test errors, always set
ENABLE_FORY_DEBUG_OUTPUT=1to see debug output. - Do not set
FORY_PANIC_ON_ERRORfor normal tests, CI reproduction, or xlang validation. It is a focused debug knob only; omit it from verification commands, but do not filter it from test harnesses when the user command provides it. - Never work around failures. Find and fix the root cause. Do not hack, weaken, or bypass tests to make them pass.
- Primary references:
README.md,CONTRIBUTING.md,docs/development/building.md, and the capability-first documentation underdocs/. - Protocol changes require reading and updating the relevant specs in
docs/specification/**and aligning the relevant cross-language tests. - If instructions conflict, follow the most specific module docs and call out the conflict.
- The
docs/tree is the canonical source for the website's Introduction, Getting Started, Benchmarks, capability guides, development guides, and separate Specification surface. - When benchmark logic, scripts, configuration, or compared serializers change, rerun the relevant benchmarks and refresh the artifacts under
docs/benchmarks/**.
- Favor zero-copy techniques, JIT or codegen opportunities, and cache-friendly memory access patterns in performance-critical paths.
- Keep hot paths allocation-minimal. Avoid per-call or per-element object allocation, boxing, wrapper round-trips, callbacks, iterator carriers, or holder objects unless there is a measured reason and no lower-allocation design preserves the same behavior.
- Keep hot-path control flow direct and predictable. Hoist repeated buffer/cache/state lookups into locals for multi-step operations, keep cold rebuild or restoration logic on slow branches, and avoid tiny forwarding helpers that only obscure the owner.
- When a language supports cold and no-inline annotations, mark cold entrances reachable from serialization hot paths so error construction, cache misses, schema mismatches, unsupported capabilities, and other slow work cannot be inlined into the hot path. Do not mark successful dynamic dispatch cold.
- In unified native/xlang hot paths, branch only where the wire format or protocol behavior actually differs. Do not add mode booleans or mode-specific helper parameters for equivalent behavior.
- Public APIs must be well-documented and easy to understand. When adding a public API, write source-level API documentation in the owning code.
- Implement comprehensive error handling with meaningful messages.
- Use strong typing and generics appropriately.
- Handle null values appropriately for each language.
- Preserve protocol compatibility across languages.
- Read and respect
docs/specification/xlang_type_mapping.mdwhen changing cross-language type behavior. - Handle byte order correctly for cross-platform compatibility.
- When a target language exposes one public integer type for narrower integer
widths or one public floating type for reduced-precision floats, use schema
metadata or field annotations to carry the exact Fory wire type. Do not add
scalar carrier wrappers for
int8,int16,int32,uint8,uint16,uint32,float16, orbfloat16. - If the reference implementation is not right, do not tweak another language's correct implementation to align with a wrong reference implementation just to make tests pass; fix the runtime that diverged from the spec.
- Use
git@github.com:apache/fory.gitas the remote repository. Do not use other remotes when you want to check code undermain;apache/mainis the only target main branch instead oforigin/main. - Treat
apache/mainas the only mainline baseline, notorigin/main. - Before any diff, review, or compare work against
apache/main, rungit fetch apache mainso comparisons use the latest remote main. - When reviewing a GitHub pull request, always do the review in a new local git worktree. Do not switch the current branch or reuse the current worktree for that review unless the user explicitly asks for it.
- Contributors should fork
git@github.com:apache/fory.git, push code changes to the fork, and open pull requests from that fork intoapache/fory.
- For Apache Fory PR, branch, commit-range, and local-diff code reviews, load
.agents/ci-and-pr.mdand follow its review workflow, red flags, and validation guidance unless explicitly acting as the independent general reviewer required byAI_POLICY.md. - When explicitly acting as the independent general reviewer required by
AI_POLICY.md, do not load.agents/ci-and-pr.mdor use copied Fory-specific review checklist prompts. Still obey review-only safety rules, this carve-out, and any general instructions required by the reviewer tool. - When the task environment supports review subagents, run Fory-guided code review through a fresh read-only review subagent while the main agent coordinates scope, checks findings, and reports the final result.
- Reuse the same review subagent for later review passes on the same feature unless a workflow explicitly requires a fresh reviewer; use a fresh review subagent for each different feature.
- Review-only tasks are read-only: do not create task files, edit files, apply patches, run tests, run builds, run benchmarks, run linters, install packages, commit, push, fix tests, or update docs unless the user explicitly starts an implementation or verification task.
- Review-only agents keep planning and findings in memory or in the final review response. They report missing validation evidence instead of running validation commands themselves.
- Run the relevant tests for every touched language or subsystem before finishing.
- A formatter-only pass after successful tests does not invalidate those test results. Do not rerun tests solely because formatting ran after the tests already passed.
- When multiple independent language test suites are required, run them concurrently when the environment has enough resources instead of running them one by one; keep each language's logs and results separate, and rerun any failed suite with focused diagnostics.
- Run applicable test commands in a subagent with a thinking budget one level lower than the main task budget, using medium when the current budget is unclear, unless the change is docs-only or the user explicitly asks to run them locally.
- Reuse the same test subagent for repeated runs within one task and subsystem so it keeps failure context; create a fresh subagent when switching unrelated subsystems or when prior context may be stale or misleading.
- Use
integration_tests/for cross-language compatibility validation when behavior crosses runtimes. - For a runtime-local xlang implementation or fixture change that does not alter the shared
protocol, type mapping, wire semantics, or another runtime, run only that runtime's Java-driven
xlang suite. For example, a Rust-only implementation change runs
org.apache.fory.xlang.RustXlangTest; do not run unrelated language peers merely because the feature operates in xlang mode. - Run the full xlang matrix only when the shared protocol, type mapping, wire semantics, or
cross-runtime behavior changes:
org.apache.fory.xlang.CPPXlangTest,org.apache.fory.xlang.CSharpXlangTest,org.apache.fory.xlang.RustXlangTest,org.apache.fory.xlang.GoXlangTest, andorg.apache.fory.xlang.PythonXlangTest. Includeorg.apache.fory.xlang.SwiftXlangTestwhen the shared change affects Swift or when Swift xlang behavior itself changes. - For performance regressions or optimizations, profile or otherwise measure the current branch and a fresh
apache/mainbaseline before changing code; optimize the measured hotspot, not guessed code. - When comparing benchmark results against
apache/main, use a separate sibling worktree namedfory-benchmark-baselineby default. Before creating a new worktree, check whether../fory-benchmark-baselinealready exists and reuse it to avoid repeated benchmark dependency rebuilds. Always fetch and sync that baseline worktree to the latestapache/mainbefore measuring it, and store benchmark result files under that worktree so older runs remain available as reference data. Treat stored benchmark results as historical references, not truth, because machine load and benchmark variance change over time. Create a different baseline worktree only when explicitly requested. - Before benchmarking a checked-out version, install or build the required Fory packages for that version, such as the Java artifacts, Python package, and the target runtime package needed by the benchmark.
- Run and close old/new benchmark comparisons for exactly one language at a time. If that language has a slowdown greater than 1%, keep working only on that language until the slowdown is within 1% before moving to the next language.
- Within one language, compare benchmarks case-by-case in adjacent old/new pairs: run one case on fresh
apache/main, then immediately run the same case on the current branch before moving to the next case. Do not batch all baseline cases and then all current cases, because machine load drift makes that comparison noisier. - Treat a same-benchmark slowdown greater than 1% as unresolved until the retained median is within 1% of the baseline. Faster results are acceptable only after verifying that generated code, benchmark shape, safety checks, and protocol semantics did not skip required work. Do not add artificial slowdowns or benchmark-shape changes to force a match.
- Do not change protocol behavior, benchmark payloads, or public APIs solely to manufacture performance wins.
- For performance work, run the relevant benchmark immediately after each change and report the command plus before/after numbers.
- For performance-optimization rounds, append the hypothesis, change, benchmark command, before/after numbers, and keep/revert decision to
tasks/perf_optimization_rounds.md. - For refactors on performance-sensitive code, validate not only tests but also that no implementation-strategy drift was introduced relative to
apache/mainunless the user explicitly asked for that change.
- Read the relevant specs, guides, and focused
.agents/*.mdfiles before editing. - Understand the affected architecture, subsystem boundaries, and existing tests before changing behavior.
- Review related issues for context when the change is tied to a known bug, regression, or feature request.
- Follow the language-specific rules in
.agents/languages/*.mdfor the touched runtimes. - Update docs, examples, and specs when public behavior, protocol behavior, or workflows change.
- Format and verify the changed areas before concluding.
- For refactors, identify the invariants that must not change before editing: behavior, protocol or wire format, implementation strategy, and performance-sensitive data structures.
- If code is already optimized, refactor around it instead of simplifying it.
- When in doubt during a refactor, prefer preserving the existing implementation over cleaning it up.
docs/: specifications, guides, benchmarks, and compiler docscompiler/: Fory compiler, parser, IR, and code generatorsjava/,csharp/,cpp/,python/,go/,rust/,swift/,javascript/,dart/,kotlin/,scala/: language implementationsintegration_tests/: cross-language integration coveragebenchmarks/: benchmark harnesses and reports.github/workflows/andci/: CI configuration and helper scripts
- After each finished task, create a git commit automatically for the task's tracked code and documentation changes, excluding
tasks/task-*.md,tasks/*-plan.md,tasks/*-state.md,tasks/lessons.md, and unrelated user changes. - PR titles must follow Conventional Commits;
.github/workflows/pr-lint.ymlenforces this. - Performance changes should use the
perftype and include benchmark data. - See
.agents/ci-and-pr.mdfor GitHub CLI triage commands and commit message examples.
User-facing security guidance lives only under Object Serialization and Fory JSON. Read
docs/object-serialization/security.md or docs/json/security.md for the selected product. Before
reporting or changing allocation, stream filling, skip, reference, metadata, or policy validation
behavior, read docs/object-serialization/deserialization-security-model.md.