# Ordered knowledge-unit consumption

`@Sequential`, `SequentialConfig`, and `SequentialUnitReader` are
`@since 0.15.0`. They first landed on framework main at
[`TnsAI@933756d1`](https://github.com/TnsAI-Framework/TnsAI/commit/933756d11d84a21c88a980406466bcca59339d16)
([PR #222](https://github.com/TnsAI-Framework/TnsAI/pull/222) /
[TAN-5880](https://linear.app/tansuasici-workspace-1/issue/TAN-5880)).
They ship in `0.15.0` and later — see
[Installation](../../start/installation.md) for the coordinates.

Use ordered consumption when a workflow must visit every ingested unit once
in a reproducible order. It is not retrieval: there is no query, ranking,
reranking, cache, or context truncation. The runtime loads the selected source,
preserves the loader-provided ingest order, then advances one target-owned
cursor. The bundled FILE pipeline sorts by normalized path and unit position;
a custom loader must return a stable order when reproducibility matters.

## Replace a parser and cursor

A hand-written scripted workflow usually owns two pieces of infrastructure:
file parsing and an integer cursor. Keep only the application rule — for
example, returning `DONE` after four interview questions — and let the RAG
runtime own ingestion and position.

Add `tnsai-intelligence` as an optional application module when you need the
runtime implementation and the programmatic reader. `@Sequential` and
`SequentialConfig` are declared in `tnsai-core`; the source loader, shared
cursor, and `SequentialUnitReader` live in `tnsai-intelligence`.

<!-- java-contract: src/main/java/com/example/tnsai/docs/SequentialConsumptionExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.annotations.ActionSpec;
import com.tnsai.annotations.KnowledgeSource;
import com.tnsai.annotations.Sequential;
import com.tnsai.enums.ActionType;
import com.tnsai.intelligence.rag.binding.SequentialUnitReader;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.rag.DocumentFormat;
import com.tnsai.rag.KnowledgeUnit;
import com.tnsai.roles.Role;

import java.util.List;
import java.util.function.Function;

public final class SequentialConsumptionExample {
    private SequentialConsumptionExample() {}

    @KnowledgeSource(
        name = "interview-questions",
        path = "knowledge/interview",
        format = DocumentFormat.TEXT,
        unit = KnowledgeUnit.LINE)
    public static final class InterviewRole extends Role {
        private static final int QUESTION_COUNT = 4;
        private int calls;

        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity(
                "interviewer", "Runs a finite interview", "Interview");
        }

        @ActionSpec(
            type = ActionType.LOCAL,
            description = "Narrate the next interview question")
        @Sequential(source = "interview-questions")
        public String nextQuestion() {
            return calls++ >= QUESTION_COUNT ? "DONE" : "";
        }

        public String nextProgrammatically() {
            return reader().next()
                .map(document -> {
                    calls++;
                    return document.content();
                })
                .orElse("DONE");
        }

        public void resetQuestions() {
            reader().reset();
            calls = 0;
        }

        private SequentialUnitReader reader() {
            return SequentialUnitReader.forTarget(
                this, "interview-questions");
        }
    }

    public static List<String> shareOneCursor(
            InterviewRole role,
            Function<InterviewRole, String> configuredActionDispatch
    ) {
        String first = configuredActionDispatch.apply(role);
        String second = role.nextProgrammatically();
        return List.of(first, second);
    }
}
```

`configuredActionDispatch` represents your application's already accountable
Agent/ActionExecutor dispatch of `nextQuestion`. That annotation-backed call
consumes the first line. The programmatic call then returns the **second** line:
`forTarget(role, "interview-questions")` resolves the same cursor by target
identity, runtime Role class, and source name.

The `calls` counter is deliberately not the source cursor. It encodes the
application-owned terminal rule. Parsing, ordering, and source position stay
inside the framework.

## SCOP path and current limitation

With an accountable executor, SCOP uses the same ActionExecutor enforcement
path: `@Sequential` writes one unit to `_rag_context`, and a blank LOCAL return
lets the existing narration fallback surface it. There is no second
SCOP-specific cursor or retrieval path.

Do not turn the callback above into a direct
`SCOPBridge.getInstance().executeAction(...)` call at this framework SHA.
`SCOPBridge` constructs a private bare `ActionExecutor`, while accountability
requires a principal, liability sink, and authority scope; the bridge exposes
no public wiring seam. The framework regression test wires those fields with
reflection, which application code must not copy. This public-runtime blocker
is tracked in
[TAN-5976](https://linear.app/tansuasici-workspace-1/issue/TAN-5976).

## Cursor contract

- **Ingest order:** exactly the order returned by the selected source loader.
  The bundled FILE loader uses normalized file path, then unit position within
  each file. Custom URL, database, or memory loaders own their stable ordering.
- **Target isolation:** a second `InterviewRole` instance starts at the first
  unit even when another instance has advanced.
- **Source isolation:** one target can advance different named sources
  independently.
- **Exhaustion:** `SequentialUnitReader.next()` returns `Optional.empty()` and
  remains empty. Annotation dispatch injects no stale narration and reports a
  retrieved-document count of zero. Your action decides whether to return
  `DONE`, another terminal value, or continue.
- **Reset:** `reset()` moves that shared target/source cursor to the first
  unit. The example also resets its separate application counter.
- **Lifetime:** cursor state is process-local. A restart begins at the first
  unit.

## Do not combine it with retrieval

An action cannot have both effective `@Sequential` and `@Retrieval`
configuration. Discovery/dispatch rejects the combination because ordered
consumption and ranked retrieval have incompatible semantics.

Choose `@Retrieval` when the input is a query and relevance should select the
context. Choose `@Sequential` when every unit must be consumed exactly in
ingest order. `@Sequential` has no equivalents of `topK`, `minScore`, rerank,
query expansion, cache TTL, or `contextWindow`; it also does not embed or
truncate a unit.

## Source evidence

- [`Sequential.java`](https://github.com/TnsAI-Framework/TnsAI/blob/933756d11d84a21c88a980406466bcca59339d16/tnsai-core/src/main/java/com/tnsai/annotations/Sequential.java)
- [`SequentialUnitReader.java`](https://github.com/TnsAI-Framework/TnsAI/blob/933756d11d84a21c88a980406466bcca59339d16/tnsai-intelligence/src/main/java/com/tnsai/intelligence/rag/binding/SequentialUnitReader.java)
- [`SequentialConsumptionTest.java`](https://github.com/TnsAI-Framework/TnsAI/blob/933756d11d84a21c88a980406466bcca59339d16/tnsai-intelligence/src/test/java/com/tnsai/intelligence/rag/binding/SequentialConsumptionTest.java)
- [`SCOPBridgeSequentialTest.java`](https://github.com/TnsAI-Framework/TnsAI/blob/933756d11d84a21c88a980406466bcca59339d16/tnsai-integration/src/test/java/com/tnsai/integration/scop/SCOPBridgeSequentialTest.java)

## Related

- [Declarative RAG](index.md) — released retrieval fields and other RAG pages
- [Knowledge Base](knowledge-base.md) — application-managed document stores
- [RAG diagnostics](diagnostics.md) — another `0.15.0` API
