You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2026/06/30 19:16:11 UTC

(tinkerpop) 01/06: Add Tiny Gremlin — local in-process traversal for gremlin-javascript

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch jsprocess
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit fb51129928845af9984923218788d01cc8a89297
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Jun 9 12:42:19 2026 -0400

    Add Tiny Gremlin — local in-process traversal for gremlin-javascript
    
    Tiny Gremlin is a defined 33-step subset of Gremlin that executes locally
    against a detached Graph object without a server. Primary use case: call
    subgraph() on a remote graph and navigate/filter the result in-process.
    Secondary use case: build small graphs manually with addV()/addE().
    
    Implementation (gremlin-javascript):
    - LocalGraphConnection: RemoteConnection adapter that accepts a Graph via
      with_(graph), parses GremlinLang with the ANTLR parser, and executes locally
    - ExecuteVisitor: walks the ANTLR parse tree into a StepDescriptor pipeline
    - LocalExecutor: chains lazy generators per step, with optimization passes
      (validation, hasId folding) and modulator folding (from/to into addE,
      by into order)
    - steps.ts: full Gremlin Orderability and Comparability semantics
    - passes.ts: validation and hasId-folding optimization passes
    - Graph.ts: outEdges/inEdges adjacency maps for O(1) neighbor lookup
    - Traversal: forEach() and reduce() terminal methods
    - @TinyGremlin Gherkin tag on existing scenarios for cross-language coverage
    
    Documentation:
    - docs/src/dev/provider/tiny-gremlin.asciidoc: full Tiny Gremlin semantics
    - docs/src/reference/the-traversal.asciidoc: Tiny Gremlin reference section
      and Tiny Gremlin Semantics links in Additional References for all 22
      covered step sections
    - docs/src/dev/provider/gremlin-semantics.asciidoc: placeholder sections
      for steps referenced by Tiny Gremlin
    
    Assisted-by: Claude Code:claude-sonnet-4-6
---
 CHANGELOG.asciidoc                                 |   1 +
 docs/src/dev/provider/gremlin-semantics.asciidoc   | 140 ++++
 docs/src/dev/provider/index.asciidoc               |   2 +
 docs/src/dev/provider/tiny-gremlin.asciidoc        | 385 +++++++++++
 docs/src/reference/the-traversal.asciidoc          | 134 +++-
 docs/static/images/tiny-gremlin.png                | Bin 0 -> 85492 bytes
 .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs |   5 +
 gremlin-go/driver/cucumber/gremlin.go              |   5 +
 .../lib/driver/local-graph-connection.ts           | 120 ++++
 gremlin-js/gremlin-javascript/lib/index.ts         |   4 +
 .../lib/language/executor/ExecuteVisitor.ts        | 708 ++++++++++++++++++++
 .../lib/process/anonymous-traversal.ts             |  13 +-
 .../lib/process/local/LocalExecutionError.ts       |  29 +
 .../lib/process/local/LocalExecutor.ts             | 248 +++++++
 .../gremlin-javascript/lib/process/local/passes.ts |  87 +++
 .../gremlin-javascript/lib/process/local/steps.ts  | 730 +++++++++++++++++++++
 .../gremlin-javascript/lib/process/local/types.ts  |  57 ++
 .../gremlin-javascript/lib/process/traversal.ts    |  28 +
 .../gremlin-javascript/lib/structure/graph.ts      |  16 +
 .../io/binary/internals/GraphSerializer.js         |   1 +
 gremlin-js/gremlin-javascript/package.json         |   1 +
 .../gremlin-javascript/test/cucumber/gremlin.js    |   7 +-
 .../test/cucumber/local-fixtures.ts                | 231 +++++++
 .../test/cucumber/local-world.ts                   |  84 +++
 .../src/main/python/tests/feature/gremlin.py       |   5 +
 .../gremlin/language/translator/translations.json  |  85 +++
 .../gremlin/test/features/filter/Has.feature       |  42 +-
 .../gremlin/test/features/filter/HasId.feature     |  12 +
 .../gremlin/test/features/filter/HasLabel.feature  |  15 +-
 .../gremlin/test/features/filter/HasNot.feature    |  17 +-
 .../gremlin/test/features/filter/Range.feature     |  14 +
 .../gremlin/test/features/filter/Tail.feature      |   4 +
 .../gremlin/test/features/map/AddEdge.feature      |   5 +-
 .../gremlin/test/features/map/AddVertex.feature    |  33 +-
 .../gremlin/test/features/map/Edge.feature         |  24 +-
 .../gremlin/test/features/map/ElementMap.feature   |   5 +-
 .../gremlin/test/features/map/Order.feature        |  59 +-
 .../gremlin/test/features/map/Path.feature         |  17 +-
 .../gremlin/test/features/map/Properties.feature   |   1 +
 .../gremlin/test/features/map/ValueMap.feature     |   6 +
 .../gremlin/test/features/map/Vertex.feature       |  31 +-
 .../test/features/semantics/Orderability.feature   |   1 +
 42 files changed, 3365 insertions(+), 47 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 963ad54f5c..b5c70c2d21 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -130,6 +130,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
 * Added the `gql-gremlin` module containing the TinkerGQL engine, a `MATCH`-pattern executor implementing a deliberate minimal subset of ISO GQL, usable by any graph provider.
 * Added `Graph.countVerticesByLabel(String)`, `Graph.countEdgesByLabel(String)`, and the `Graph.Index` nested interface as performance-hint extension points for the TinkerGQL engine.
 * Added GQL support to TinkerGraph with `gql-gremlin`,
+* Added Tiny Gremlin local execution to `gremlin-javascript`: `traversal().with_(graph)` now executes a defined subset of Gremlin steps (navigation, filtering, value extraction, path tracking, range, ordering, and local mutation) against an in-memory `Graph` without a remote server. The `Graph` adjacency index (`outEdges`, `inEdges`) enables O(1) navigation. New `forEach(fn)` and `reduce(fn, seed)` terminal methods on `Traversal` support result processing with native JavaScript functions [...]
 
 [[release-4-0-0-beta-2]]
 === TinkerPop 4.0.0-beta.2 (April 1, 2026)
diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc
index 8b25408ff2..f551caf060 100644
--- a/docs/src/dev/provider/gremlin-semantics.asciidoc
+++ b/docs/src/dev/provider/gremlin-semantics.asciidoc
@@ -504,6 +504,146 @@ and the corresponding reference documentation. When a step has no entries for `A
 the field is still present with `None` as its body. When adding a new step or revising an existing one, follow the
 pattern from a neighboring entry rather than introducing a new structure.
 
+[[e-step]]
+=== E()
+
+To be written...
+
+[[v-step]]
+=== V()
+
+To be written...
+
+[[elementmap-step]]
+=== elementMap()
+
+To be written...
+
+[[has-step]]
+=== has()
+
+To be written...
+
+[[hasid-step]]
+=== hasId()
+
+To be written...
+
+[[haskey-step]]
+=== hasKey()
+
+To be written...
+
+[[haslabel-step]]
+=== hasLabel()
+
+To be written...
+
+[[hasnot-step]]
+=== hasNot()
+
+To be written...
+
+[[hasvalue-step]]
+=== hasValue()
+
+To be written...
+
+[[id-step]]
+=== id()
+
+To be written...
+
+[[ine-step]]
+=== inE()
+
+To be written...
+
+[[inv-step]]
+=== inV()
+
+To be written...
+
+[[in-step]]
+=== in()
+
+To be written...
+
+[[key-step]]
+=== key()
+
+To be written...
+
+[[label-step]]
+=== label()
+
+To be written...
+
+[[limit-step]]
+=== limit()
+
+To be written...
+
+[[order-step]]
+=== order()
+
+To be written...
+
+[[otherv-step]]
+=== otherV()
+
+To be written...
+
+[[out-step]]
+=== out()
+
+To be written...
+
+[[oute-step]]
+=== outE()
+
+To be written...
+
+[[outv-step]]
+=== outV()
+
+To be written...
+
+[[path-step]]
+=== path()
+
+To be written...
+
+[[property-step]]
+=== property()
+
+To be written...
+
+[[range-step]]
+=== range()
+
+To be written...
+
+[[skip-step]]
+=== skip()
+
+To be written...
+
+[[tail-step]]
+=== tail()
+
+To be written...
+
+[[value-step]]
+=== value()
+
+To be written...
+
+[[values-step]]
+=== values()
+
+To be written...
+
 [[adde-step]]
 === addE()
 
diff --git a/docs/src/dev/provider/index.asciidoc b/docs/src/dev/provider/index.asciidoc
index a09c67b915..c55bdc0033 100644
--- a/docs/src/dev/provider/index.asciidoc
+++ b/docs/src/dev/provider/index.asciidoc
@@ -1903,4 +1903,6 @@ in use with the Gremlin Console plugin host.  Simply instantiate and return a `R
 
 include::gremlin-semantics.asciidoc[]
 
+include::tiny-gremlin.asciidoc[]
+
 include::policies.asciidoc[]
\ No newline at end of file
diff --git a/docs/src/dev/provider/tiny-gremlin.asciidoc b/docs/src/dev/provider/tiny-gremlin.asciidoc
new file mode 100644
index 0000000000..2dd151598a
--- /dev/null
+++ b/docs/src/dev/provider/tiny-gremlin.asciidoc
@@ -0,0 +1,385 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+
+[[tiny-gremlin]]
+= Tiny Gremlin
+
+image:tiny-gremlin.png[]
+
+== Introduction
+
+*Tiny Gremlin* is a defined subset of the Gremlin traversal language intended for local, in-process graph traversal
+against small, detached `Graph` instances. A common use case would be to take the result of a `subgraph()` call and then
+locally traverse it with Tiny Gremlin. It specifies which steps must be supported, the precise semantics of each step
+within the local execution model, and any deviations from the full Gremlin standard that implementors must be aware of.
+
+=== Motivation
+
+The primary use case for Tiny Gremlin is post-processing of subgraphs received from a remote graph server without
+requiring an additional network round-trip. A user retrieves a subgraph, holds it in memory as a `Graph` object, and
+then navigates and filters it locally using familiar Gremlin syntax. A secondary use case is constructing small graphs
+entirely in process for lightweight analysis or testing, using `addV()`, `addE()`, and `property()`.
+
+Because the graph is small and entirely in memory, features that are expensive or architecture-dependent in a full
+implementation, such as `repeat()`, distributed OLAP steps, aggregation barriers, and strategy-based optimizations,
+are deliberately excluded. The result is a small, self-contained, predictable execution model.
+
+=== Supported Steps
+
+Tiny Gremlin supports exactly the 33 steps listed below, grouped by category. No other step may be used in a Tiny
+Gremlin traversal; doing so results in an error before any graph data is accessed.
+
+[width="100%",options="header"]
+|===
+|Category |Steps
+|Source |`V()`, `E()`
+|Navigation |`out()`, `in()`, `both()`, `outE()`, `inE()`, `bothE()`, `outV()`, `inV()`, `otherV()`
+|Filter |`has()`, `hasId()`, `hasLabel()`, `hasNot()`
+|Value Extraction |`id()`, `label()`, `values()`, `valueMap()`, `elementMap()`, `value()`, `key()`
+|Path |`path()`
+|Range |`limit()`, `range()`, `skip()`, `tail()`
+|Ordering |`order()`
+|Mutation |`addV()`, `addE()`, `property()`
+|Modulators |`from()`, `to()`
+|===
+
+=== Terminal Methods
+
+Tiny Gremlin must support the standard `toList()`, `next()`, and `iterate()` terminal methods, but should offer
+language-specific terminals designed to help with stream processing ergonomics. For example, in Javascript, TinkerPop
+offers:
+
+* `forEach(fn)` — calls `fn` for each result element; returns `Promise<void>`.
+* `reduce(fn, seed)` — folds all results into a single value using `fn` and an initial `seed`; returns `Promise<T>`.
+
+Since Tiny Gremlin lacks steps needed for data transformation and filtering, these added steps will allow users to
+express post-processing functions on the result set.
+
+== Step Semantics
+
+The following sub-sections describe each supported step, its relationship to the full Gremlin Semantics definition,
+and any limitations or behavioral differences that apply specifically to Tiny Gremlin.
+
+=== addE()
+
+*Gremlin Semantics reference:* <<adde-step,addE()>>
+
+Creates a new edge in the local `Graph` between vertices specified by `from()` and `to()` modulators.
+
+*Tiny Gremlin limitations:*
+
+* Only the `addE(String edgeLabel)` form is supported. The `addE(Traversal)` label-traversal form is not supported
+  because the traversal would need to reference prior labeled elements via `as()` and `select()`, neither of which
+  is in the Tiny Gremlin step set.
+* `from()` and `to()` must use the anonymous traversal form (e.g. `from(__.V(id))`). The step-label form
+  (`from("stepLabel")`) is not supported because `as()` is not in the Tiny Gremlin step set. The GLV convenience
+  sugar `from_(vertexObject)` does not work because the GLV converts the `Vertex` to its raw ID before
+  serialization, producing `from(id)` which is not a valid grammar form and results in a parse error.
+* The vertex referenced by `from()` or `to()` must exist in the local `Graph` at execution time. If the ID is not
+  found, an error is raised. No vertex is created implicitly.
+
+=== addV()
+
+*Gremlin Semantics reference:* <<addv-step,addV()>>
+
+Creates a new vertex in the local `Graph`.
+
+*Tiny Gremlin limitations:*
+
+* No cardinality-aware property form (`property(Cardinality, key, value)`) is supported; only the simple
+  `property(key, value)` form applies to vertices created by `addV()`.
+* Changes are local only; the remote graph is not modified.
+
+=== both()
+
+*Gremlin Semantics reference:* <<both-step,both()>>
+
+Maps a vertex to its adjacent vertices, traversing both incoming and outgoing edges.
+
+=== bothE()
+
+*Gremlin Semantics reference:* <<bothe-step,bothE()>>
+
+Maps a vertex to its incident edges, both incoming and outgoing.
+
+=== E()
+
+*Gremlin Semantics reference:* <<e-step,E()>>
+
+Produces edges from the local graph. When called with no arguments, all edges are produced. When called with one or
+more IDs, only matching edges are produced. A single list argument is also accepted.
+
+=== elementMap()
+
+*Gremlin Semantics reference:* <<elementmap-step,elementMap()>>
+
+Maps an element to a `Map` containing its `T.id`, `T.label`, and property key-value pairs. For edges,
+`Direction.OUT` and `Direction.IN` keys are included as stub maps containing `T.id` and `T.label` of the adjacent
+vertices.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported.
+
+=== has()
+
+*Gremlin Semantics reference:* <<has-step,has()>>
+
+Filters elements by property existence, equality, or predicate.
+
+Supported forms:
+
+* `has(key)` — element has the property.
+* `has(key, value)` — element has the property with the given value.
+* `has(key, P)` — element has the property satisfying the predicate.
+* `has(label, key, value)` — element has the given label and the property with the given value.
+* `has(label, key, P)` — element has the given label and the property satisfying the predicate.
+
+Supported `P` predicates: `eq`, `neq`, `lt`, `lte`, `gt`, `gte`, `between`, `inside`, `outside`, `within`,
+`without`, `not`. Supported `TextP` predicates: `containing`, `notContaining`, `startingWith`, `notStartingWith`,
+`endingWith`, `notEndingWith`, `regex`, `notRegex`.
+
+The ordering predicates (`lt`, `lte`, `gt`, `gte`, `between`, `inside`, `outside`) follow the
+<<gremlin-semantics-concepts,Gremlin Comparability semantics>>: cross-type comparisons and comparisons involving
+`NaN` return `false` (the comparability ERROR result).
+
+*Tiny Gremlin limitations:*
+
+* Only `T.id` and `T.label` are supported as key tokens. `T.key` and `T.value` are not supported because
+  `properties()` is not in the Tiny Gremlin step set and there is no other way to produce `Property` traversers.
+
+=== hasId()
+
+*Gremlin Semantics reference:* <<hasid-step,hasId()>>
+
+Filters elements by their ID.
+
+*Tiny Gremlin limitations:*
+
+* `hasId([1, 2, 3])` — a single list argument — is unrolled and treated as `hasId(1, 2, 3)`, consistent with
+  `V([1,2,3])` semantics.
+* When more than one argument is provided, each argument is treated as a distinct ID value. A list in that position
+  is compared as-is and will not match any scalar ID (e.g. `hasId(1, [2, 3])` matches only id `1`). This
+  intentionally differs from the standard `hasId()` implementation which unrolls lists in all positions (see
+  TINKERPOP-2863).
+
+=== hasKey()
+
+*Gremlin Semantics reference:* <<haskey-step,hasKey()>>
+
+Filters `Property` traversers by key. Operates on the output of `properties()`.
+
+=== hasLabel()
+
+*Gremlin Semantics reference:* <<haslabel-step,hasLabel()>>
+
+Filters elements by label.
+
+=== hasNot()
+
+*Gremlin Semantics reference:* <<hasnot-step,hasNot()>>
+
+Filters elements by the absence of a property.
+
+=== hasValue()
+
+*Gremlin Semantics reference:* <<hasvalue-step,hasValue()>>
+
+Filters `Property` traversers by value. Operates on the output of `properties()`.
+
+=== id()
+
+*Gremlin Semantics reference:* <<id-step,id()>>
+
+Maps an element to its ID.
+
+=== in()
+
+*Gremlin Semantics reference:* <<in-step,in()>>
+
+Maps a vertex to its adjacent incoming vertices.
+
+=== inE()
+
+*Gremlin Semantics reference:* <<ine-step,inE()>>
+
+Maps a vertex to its incoming edges.
+
+=== inV()
+
+*Gremlin Semantics reference:* <<inv-step,inV()>>
+
+Maps an edge to its incoming vertex (the head of the arrow).
+
+=== key()
+
+*Gremlin Semantics reference:* <<key-step,key()>>
+
+Maps a `Property` traverser to its key.
+
+=== label()
+
+*Gremlin Semantics reference:* <<label-step,label()>>
+
+Maps an element to its label.
+
+=== limit()
+
+*Gremlin Semantics reference:* <<limit-step,limit()>>
+
+Truncates the result stream to at most `n` elements.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== order()
+
+*Gremlin Semantics reference:* <<order-step,order()>>
+
+Sorts all elements.
+
+Supports a single `by()` modulator with the following forms:
+
+* `by(String key)` — sort by property value ascending.
+* `by(String key, Order)` — sort by property value with explicit direction (`Order.asc` or `Order.desc`).
+* `by(T.id)` / `by(T.id, Order)` and `by(T.label)` / `by(T.label, Order)` — sort by the element's ID or label
+  with optional direction.
+* `by(Order)` — sort by natural element value with explicit direction.
+* `by()` — natural ascending sort (same as omitting `by()`).
+
+*Tiny Gremlin limitations:*
+
+* Only a single `by()` modulator is supported per `order()`.
+* Ordering follows the <<gremlin-semantics-orderability,Gremlin Orderability semantics>>, providing a total order
+  across all types: `null` < `Boolean` < `Number` < `Date` < `String` < `Vertex` < `Edge` < `VertexProperty`
+  < `Property` < `Path` < `Set` < `List` < `Map` < `Unknown`. `NaN` is ordered after `+Infinity` within the
+  numeric type space.
+* The `Scope` form (`order(Scope)`) is not supported and will raise an error.
+* `by(Traversal)`, `by(Traversal, Order)`, and `by(Function)` are not supported and will raise an error.
+
+=== otherV()
+
+*Gremlin Semantics reference:* <<otherv-step,otherV()>>
+
+Maps an edge to the vertex that was not the vertex traversed from.
+
+=== out()
+
+*Gremlin Semantics reference:* <<out-step,out()>>
+
+Maps a vertex to its adjacent outgoing vertices.
+
+=== outE()
+
+*Gremlin Semantics reference:* <<oute-step,outE()>>
+
+Maps a vertex to its outgoing edges.
+
+=== outV()
+
+*Gremlin Semantics reference:* <<outv-step,outV()>>
+
+Maps an edge to its outgoing vertex (the tail of the arrow).
+
+=== path()
+
+*Gremlin Semantics reference:* <<path-step,path()>>
+
+Maps the current traverser to the `Path` of elements it has visited.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported. Path objects always contain the raw elements visited.
+* `from()` and `to()` selection within a path is not supported because `as()` step labels are excluded from Tiny
+  Gremlin.
+
+=== property()
+
+*Gremlin Semantics reference:* <<property-step,property()>>
+
+Sets a property on the current element.
+
+*Tiny Gremlin limitations:*
+
+* Only the `property(key, value)` two-argument form is supported. The cardinality form
+  `property(Cardinality, key, value)` and the map form `property(map)` are not supported.
+* For vertices, an existing property with the same key is replaced (single cardinality semantics regardless of graph
+  configuration).
+
+=== range()
+
+*Gremlin Semantics reference:* <<range-step,range()>>
+
+Emits elements at positions `[low, high)` in the stream, zero-indexed.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== skip()
+
+*Gremlin Semantics reference:* <<skip-step,skip()>>
+
+Discards the first `n` elements of the stream.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== tail()
+
+*Gremlin Semantics reference:* <<tail-step,tail()>>
+
+Emits the last `n` elements of the stream. This is a barrier step.
+
+*Tiny Gremlin limitations:*
+
+* The `Scope` form is not supported and will raise an error.
+
+=== V()
+
+*Gremlin Semantics reference:* <<v-step,V()>>
+
+Produces vertices from the local graph. When called with no arguments, all vertices are produced. When called with one
+or more IDs, only matching vertices are produced. A single list argument is also accepted.
+
+=== value()
+
+*Gremlin Semantics reference:* <<value-step,value()>>
+
+Maps a `Property` traverser to its value.
+
+=== valueMap()
+
+*Gremlin Semantics reference:* <<valueMap-step,valueMap()>>
+
+Maps an element to a `Map` of property keys to lists of property values.
+
+*Tiny Gremlin limitations:*
+
+* `by()` modulation is not supported.
+* `with(WithOptions.tokens)` configuration is not supported; use `valueMap(true)` instead.
+
+=== values()
+
+*Gremlin Semantics reference:* <<values-step,values()>>
+
+Maps an element to the values of its properties.
+
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index 5db65c45db..8465103f13 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -638,7 +638,8 @@ g.addE('knows').from(__.V(1)).to(__.constant(6)) <7>
 *Additional References*
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(java.lang.String)++[`addE(String)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addE(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addE(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addE(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[addvertex-step]]
 === AddV Step
@@ -659,7 +660,8 @@ g.V().has('name','nothing').bothE()
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV()++[`addV()`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(java.lang.String)++[`addV(String)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addV(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#addV(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`addV(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[aggregate-step]]
 === [[store-step]]Aggregate Step
@@ -1808,7 +1810,8 @@ g.inject(1).coalesce(E().hasLabel("knows"), addE("knows").from(V().has("name","j
 *Additional References*
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(java.lang.Object...)++[`E(Object...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`E(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#E(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`E(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[element-step]]
 === Element Step
@@ -1859,7 +1862,8 @@ as the `id` is the only available data to the star graph.
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#elementMap(java.lang.String...)++[`elementMap(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#elementMap(java.lang.String...)++[`elementMap(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[emit-step]]
 === Emit Step
@@ -2059,7 +2063,8 @@ The term `from` is a reserved word in Python, and therefore must be referred to
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(java.lang.String)++[`from(String)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`from(Traversal)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.structure.Vertex)++[`from(Vertex)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#from(org.apache.tinkerpop.gremlin.structure.Vertex)++[`from(Vertex)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[group-step]]
 === Group Step
@@ -2229,7 +2234,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#hasValue(org.apache.tinkerpop.gremlin.process.traversal.P)++[`hasValue(P)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/P.html++[`P`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/TextP.html++[`TextP`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/T.html++[`T`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[id-step]]
 === Id Step
@@ -2246,7 +2252,8 @@ g.V(1).properties().id()
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#id()++[`id()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#id()++[`id()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[identity-step]]
 === Identity Step
@@ -2588,7 +2595,8 @@ g.V(1).properties().properties().key()
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#key()++[`key()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#key()++[`key()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[label-step]]
 === Label Step
@@ -2604,7 +2612,8 @@ g.V(1).properties().label()
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#label()++[`label()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#label()++[`label()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[length-step]]
 === Length Step
@@ -2656,8 +2665,9 @@ g.V().valueMap().select('location').limit(local, 1).unfold() <4>
 *Additional References*
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(long)++[`limit(long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`limit(Scope,long)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#limit(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`limit(Scope,long)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[local-step]]
 === Local Step
@@ -3810,7 +3820,8 @@ order. Those tokens were deprecated and eventually removed in 3.5.0.
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#order()++[`order()`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#order(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`order(Scope)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Order.html++[`Order`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[pagerank-step]]
 === PageRank Step
@@ -3951,7 +3962,8 @@ path.d == path.e
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#path()++[`path()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#path()++[`path()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[peerpressure-step]]
 === PeerPressure Step
@@ -4223,7 +4235,8 @@ mutating steps (`IllegalArgumentException`) and must produce a `Map` (`IllegalSt
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#property(java.lang.Object,java.lang.Object,java.lang.Object...)++[`property(Object, Object, Object...)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#property(org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality,java.lang.Object,java.lang.Object,java.lang.Object...)++[`property(Cardinality, Object, Object, Object...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/VertexProperty.Cardinality.html++[`Cardinality`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/VertexProperty.Cardinality.html++[`Cardinality`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[propertymap-step]]
 === PropertyMap Step
@@ -4286,7 +4299,8 @@ g.V().valueMap().select('location').range(local, 1, 2).unfold() <3>
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#range(long,long)++[`range(long,long)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#range(org.apache.tinkerpop.gremlin.process.traversal.Scope,long,long)++[`range(Scope,long,long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[read-step]]
 === Read Step
@@ -4922,7 +4936,8 @@ g.V().hasLabel('person').filter(outE('created')).as('p'). <1>
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip(long)++[`skip(long)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#skip(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`skip(Scope,long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[split-step]]
 === Split Step
@@ -5141,9 +5156,10 @@ g.V().elementMap().tail(local) <6>
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail()++[`tail()`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(long)++[`tail(long)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`tail(Scope)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`tail(Scope,long)`]
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope)++[`tail(Scope)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#tail(org.apache.tinkerpop.gremlin.process.traversal.Scope,long)++[`tail(Scope,long)`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/Scope.html++[`Scope`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[timelimit-step]]
 === TimeLimit Step
@@ -5205,7 +5221,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#to(org.apache.tinkerpop.gremlin.structure.Vertex)++[`to(Vertex)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#toE(org.apache.tinkerpop.gremlin.structure.Direction,java.lang.String...)++[`toE(Direction,String)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#toV(org.apache.tinkerpop.gremlin.structure.Direction)++[`toV(Direction)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/Direction.html++[`Direction`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/structure/Direction.html++[`Direction`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[toLower-step]]
 === ToLower Step
@@ -5430,7 +5447,8 @@ g.V().has('name', within('marko', 'vadas', 'josh')).as('person').
 *Additional References*
 
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(java.lang.Object...)++[`V(Object...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`V(Traversal)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#V(org.apache.tinkerpop.gremlin.process.traversal.Traversal)++[`V(Traversal)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[value-step]]
 === Value Step
@@ -5445,7 +5463,8 @@ g.V(1).properties().properties().value()
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#value()++[`value()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#value()++[`value()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[valuemap-step]]
 === ValueMap Step
@@ -5496,7 +5515,8 @@ g.V().hasLabel('person').properties('location').valueMap().with(WithOptions.toke
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#valueMap(java.lang.String...)++[`valueMap(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#valueMap(java.lang.String...)++[`valueMap(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[values-step]]
 === Values Step
@@ -5512,7 +5532,8 @@ g.V(1).properties('location').values()
 
 *Additional References*
 
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#values(java.lang.String...)++[`values(String...)`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#values(java.lang.String...)++[`values(String...)`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[vertex-steps]]
 === Vertex Steps
@@ -5581,7 +5602,8 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#otherV()++[`otherV()`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#out(java.lang.String...)++[`out(String...)`],
 link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outE(java.lang.String...)++[`outE(String...)`],
-link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outV()++[`outV()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#outV()++[`outV()`],
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics]
 
 [[where-step]]
 === Where Step
@@ -6839,3 +6861,65 @@ t = g.V().has_label('person')
 script = t.gremlin_lang.get_gremlin()
 # Returns: "g.V().hasLabel('person')"
 ----
+
+[[tiny-gremlin-traversal]]
+== Tiny Gremlin
+
+*Tiny Gremlin* is a defined subset of the Gremlin traversal language that executes locally and in-process against a
+small, detached `Graph` instance. The most common use case is to call `subgraph()` on a remote graph, receive a `Graph`
+object, and then navigate and filter it locally using familiar Gremlin syntax without an additional network round-trip.
+A secondary use case is constructing small graphs entirely in-process using `addV()`, `addE()`, and `property()`.
+
+Full details on semantics, supported forms, and limitations are covered in the
+link:https://tinkerpop.apache.org/docs/x.y.z/dev/provider/#tiny-gremlin[Tiny Gremlin Semantics] section of the
+Provider Documentation. Tiny Gremlin is currently only supported in `gremlin-javascript`.
+
+=== Supported Steps
+
+Tiny Gremlin supports exactly 33 steps. No other step may be used in a Tiny Gremlin traversal; attempting to do so
+results in an error before any graph data is accessed.
+
+[width="100%",options="header"]
+|===
+|Category |Steps
+|Source |<<v-step,`V()`>>, <<e-step,`E()`>>
+|Navigation |<<vertex-steps,`out()`>>, <<vertex-steps,`in()`>>, <<vertex-steps,`both()`>>, <<vertex-steps,`outE()`>>, <<vertex-steps,`inE()`>>, <<vertex-steps,`bothE()`>>, <<vertex-steps,`outV()`>>, <<vertex-steps,`inV()`>>, <<vertex-steps,`otherV()`>>
+|Filter |<<has-step,`has()`>>, <<has-step,`hasId()`>>, <<has-step,`hasLabel()`>>, <<has-step,`hasNot()`>>
+|Value Extraction |<<id-step,`id()`>>, <<label-step,`label()`>>, <<values-step,`values()`>>, <<valuemap-step,`valueMap()`>>, <<elementmap-step,`elementMap()`>>, <<value-step,`value()`>>, <<key-step,`key()`>>
+|Path |<<path-step,`path()`>>
+|Range |<<limit-step,`limit()`>>, <<range-step,`range()`>>, <<skip-step,`skip()`>>, <<tail-step,`tail()`>>
+|Ordering |<<order-step,`order()`>>
+|Mutation |<<addvertex-step,`addV()`>>, <<addedge-step,`addE()`>>, <<property-step,`property()`>>
+|Modulators |<<from-step,`from()`>>, <<to-step,`to()`>>
+|===
+
+=== Language Support
+
+Tiny Gremlin is currently only implemented in `gremlin-javascript`. The `with_(graph)` API accepts a local `Graph`
+object in place of a remote connection, transparently enabling local traversal using the same Gremlin API:
+
+[source,javascript]
+----
+const { graph: { Graph }, process: { anonymousTraversal: { traversal } } } = require('gremlin');
+
+const graph = new Graph();
+const g = traversal().with_(graph);
+
+// Build a small graph and query it locally
+const names = await g.addV('person').property('name', 'alice')
+                      .V().values('name').toList();
+// Returns: ['alice']
+----
+
+The primary use case of processing a subgraph received from a remote server requires no code changes other than
+passing the returned `Graph` object to `with_()`:
+
+[source,javascript]
+----
+// Retrieve a subgraph from a remote server
+const subgraph = await gRemote.V().hasLabel('person').subgraph('sg').cap('sg').next();
+
+// Query it locally with Tiny Gremlin — no additional network calls
+const g = traversal().with_(subgraph);
+const paths = await g.V().out('knows').in('knows').path().toList();
+----
diff --git a/docs/static/images/tiny-gremlin.png b/docs/static/images/tiny-gremlin.png
new file mode 100644
index 0000000000..67a53f8984
Binary files /dev/null and b/docs/static/images/tiny-gremlin.png differ
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index e6238de9a6..0d610f47d9 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -563,6 +563,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_E_hasXlabel_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E().Has(T.Label, (object) null)}}, 
                {"g_V_properties_hasLabelXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasLabel((string) null)}}, 
                {"g_V_hasNotXageX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasNot("age").Values<object>("name")}}, 
+               {"g_V_hasNotXlangX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasNot("lang").Values<object>("name")}}, 
                {"g_V_properties_hasValueXnullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue((object) null)}}, 
                {"g_V_properties_hasValueXnull_nullX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue(null, null)}}, 
                {"g_V_properties_hasValueXnull_joshX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().HasValue(null, "josh").Value<object>()}}, 
@@ -1645,6 +1646,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).ElementMap<object>().Order(Scope.Local).By(Column.Keys, Order.Desc).Unfold<object>()}}, 
                {"g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).ElementMap<object>().Order(Scope.Local).By(Column.Keys, Order.Asc).Unfold<object>()}}, 
                {"g_VX1X_valuesXageX_orderXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Values<object>("age").Order(Scope.Local)}}, 
+               {"g_V_order_byXTidX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Id).Values<object>("name")}}, 
+               {"g_V_order_byXTid_descX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Id, Order.Desc).Values<object>("name")}}, 
+               {"g_V_order_byXTlabelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Order().By(T.Label).Label()}}, 
                {"g_V_pageRank_hasXpageRankX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PageRank().Has("gremlin.pageRankVertexProgram.pageRank")}}, 
                {"g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out("created").PageRank().With("~tinkerpop.pageRank.edges", __.BothE()).With("~tinkerpop.pageRank.propertyName", "projectRank").With("~tinkerpop.pageRank.times", 0).ValueMap<object, object>("name", "projectRank")}}, 
                {"g_V_pageRank_order_byXpageRank_descX_byXnameX_name", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PageRank().Order().By("gremlin.pageRankVertexProgram.pageRank", Order.Desc).By("name").Values<object>("name")}}, 
@@ -1665,6 +1669,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
                {"g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}}, 
                {"g_injectX1_null_nullX_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1, null, null).Path()}}, 
                {"g_injectX1_null_nullX_path_dedup", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1, null, null).Path().Dedup()}}, 
+               {"g_VX1X_out_out_path", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).Out().Out().Path()}}, 
                {"g_V_peerPressure_hasXclusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PeerPressure().Has("gremlin.peerPressureVertexProgram.cluster")}}, 
                {"g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().PeerPressure().With("~tinkerpop.peerPressure.propertyName", "cluster").With("~tinkerpop.peerPressure.edges", __.OutE("knows")).PageRank(1.0).With("~tinkerpop.pageRank.propertyName", "rank").With("~tinkerpop.pageR [...]
                {"g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", "ripple").In("created").PeerPressure().With("~tinkerpop.peerPressure.edges", __.OutE()).With("~tinkerpop.peerPressure.propertyName", "cluster").Repeat(__.Union<object>(__.Identity(), __.Both())).Times(2).Dedup( [...]
diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go
index 82f5ff777b..70822b8eba 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -533,6 +533,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_E_hasXlabel_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.E().Has(gremlingo.T.Label, nil)}}, 
     "g_V_properties_hasLabelXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasLabel(nil)}}, 
     "g_V_hasNotXageX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasNot("age").Values("name")}}, 
+    "g_V_hasNotXlangX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasNot("lang").Values("name")}}, 
     "g_V_properties_hasValueXnullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil)}}, 
     "g_V_properties_hasValueXnull_nullX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil, nil)}}, 
     "g_V_properties_hasValueXnull_joshX_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().HasValue(nil, "josh").Value()}}, 
@@ -1615,6 +1616,9 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).ElementMap().Order(gremlingo.Scope.Local).By(gremlingo.Column.Keys, gremlingo.Order.Desc).Unfold()}}, 
     "g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).ElementMap().Order(gremlingo.Scope.Local).By(gremlingo.Column.Keys, gremlingo.Order.Asc).Unfold()}}, 
     "g_VX1X_valuesXageX_orderXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Values("age").Order(gremlingo.Scope.Local)}}, 
+    "g_V_order_byXTidX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Id).Values("name")}}, 
+    "g_V_order_byXTid_descX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Id, gremlingo.Order.Desc).Values("name")}}, 
+    "g_V_order_byXTlabelX_label": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Order().By(gremlingo.T.Label).Label()}}, 
     "g_V_pageRank_hasXpageRankX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PageRank().Has("gremlin.pageRankVertexProgram.pageRank")}}, 
     "g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out("created").PageRank().With("~tinkerpop.pageRank.edges", gremlingo.T__.BothE()).With("~tinkerpop.pageRank.propertyName", "projectRank").With("~tinkerpop.pageRank.times", 0).ValueMap("name", "projectRank")}}, 
     "g_V_pageRank_order_byXpageRank_descX_byXnameX_name": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PageRank().Order().By("gremlin.pageRankVertexProgram.pageRank", gremlingo.Order.Desc).By("name").Values("name")}}, 
@@ -1635,6 +1639,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
     "g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.ProductiveByStrategy()).V(p["vid1"]).Out().Path().By("age")}}, 
     "g_injectX1_null_nullX_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1, nil, nil).Path()}}, 
     "g_injectX1_null_nullX_path_dedup": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1, nil, nil).Path().Dedup()}}, 
+    "g_VX1X_out_out_path": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).Out().Out().Path()}}, 
     "g_V_peerPressure_hasXclusterX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PeerPressure().Has("gremlin.peerPressureVertexProgram.cluster")}}, 
     "g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().PeerPressure().With("~tinkerpop.peerPressure.propertyName", "cluster").With("~tinkerpop.peerPressure.edges", gremlingo.T__.OutE("knows")).PageRank(1.0).With("~tinkerpop.pageRank.propertyName", "rank").With("~ti [...]
     "g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", "ripple").In("created").PeerPressure().With("~tinkerpop.peerPressure.edges", gremlingo.T__.OutE()).With("~tinkerpop.peerPressure.propertyName", "cluster").Repeat(gremlingo.T__.Union(gremlingo.T__.Identity(),  [...]
diff --git a/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
new file mode 100644
index 0000000000..0828caf8f9
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/driver/local-graph-connection.ts
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+  ATNSimulator, BaseErrorListener, CharStream, CommonTokenStream,
+  PredictionMode, Recognizer, RecognitionException,
+} from 'antlr4ng';
+import { GremlinLexer } from '../language/grammar/GremlinLexer.js';
+import { GremlinParser } from '../language/grammar/GremlinParser.js';
+import { ExecuteVisitor } from '../language/executor/ExecuteVisitor.js';
+import { LocalExecutor } from '../process/local/LocalExecutor.js';
+import { RemoteConnection, RemoteTraversal } from './remote-connection.js';
+import { Graph } from '../structure/graph.js';
+import type GremlinLang from '../process/gremlin-lang.js';
+
+class ThrowingErrorListener extends BaseErrorListener {
+  override syntaxError(
+    _recognizer: Recognizer<ATNSimulator>,
+    _offendingSymbol: unknown,
+    line: number,
+    column: number,
+    msg: string,
+    _e: RecognitionException | null,
+  ): void {
+    throw new Error(`Tiny Gremlin parse error at ${line}:${column} — ${msg}`);
+  }
+}
+
+const errorListener = new ThrowingErrorListener();
+
+/**
+ * A RemoteConnection implementation that executes traversals locally against an
+ * in-memory Graph using the Tiny Gremlin subset. No network is involved.
+ *
+ * Usage:
+ *   const g = anon.traversal().with_(myGraph);
+ *   // or equivalently:
+ *   const g = anon.traversal().with_(new LocalGraphConnection(myGraph));
+ */
+export class LocalGraphConnection extends RemoteConnection {
+  private readonly graph: Graph;
+  private readonly executor: LocalExecutor;
+
+  constructor(graph: Graph) {
+    super('local://');
+    this.graph = graph;
+    this.executor = new LocalExecutor();
+  }
+
+  override async open(): Promise<void> {
+    // no-op: no connection to open
+  }
+
+  override get isOpen(): boolean {
+    return true;
+  }
+
+  override async submit(gremlinLang: GremlinLang): Promise<RemoteTraversal> {
+    const query = gremlinLang.getGremlin();
+    const pipeline = this.parse(query);
+    const generator = this.executor.execute(pipeline, this.graph);
+    return new RemoteTraversal(generator);
+  }
+
+  override async commit(): Promise<void> {
+    // no-op: local graph mutations are in-memory only
+  }
+
+  override async rollback(): Promise<void> {
+    // no-op
+  }
+
+  override async close(): Promise<void> {
+    // no-op
+  }
+
+  private parse(query: string) {
+    const chars = CharStream.fromString(query);
+    const lexer = new GremlinLexer(chars);
+    lexer.removeErrorListeners();
+    lexer.addErrorListener(errorListener);
+
+    const tokens = new CommonTokenStream(lexer);
+    const parser = new GremlinParser(tokens);
+    parser.interpreter.predictionMode = PredictionMode.SLL;
+    parser.removeErrorListeners();
+    parser.addErrorListener(errorListener);
+
+    let queryCtx: ReturnType<typeof parser.queryList>;
+    try {
+      queryCtx = parser.queryList();
+    } catch {
+      tokens.seek(0);
+      lexer.reset();
+      parser.reset();
+      parser.interpreter.predictionMode = PredictionMode.LL;
+      queryCtx = parser.queryList();
+    }
+
+    const visitor = new ExecuteVisitor();
+    visitor.visit(queryCtx);
+    return visitor.getPipeline();
+  }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/index.ts b/gremlin-js/gremlin-javascript/lib/index.ts
index a2443b8d1d..3498fa59a3 100644
--- a/gremlin-js/gremlin-javascript/lib/index.ts
+++ b/gremlin-js/gremlin-javascript/lib/index.ts
@@ -36,6 +36,8 @@ import ResultSet from './driver/result-set.js';
 import { HttpRequest } from './driver/http-request.js';
 import { basic, sigv4 } from './driver/auth.js';
 import AnonymousTraversalSource from './process/anonymous-traversal.js';
+import { LocalGraphConnection } from './driver/local-graph-connection.js';
+import { LocalExecutionError } from './process/local/LocalExecutionError.js';
 
 export const driver = {
   RemoteConnection: rc.RemoteConnection,
@@ -43,6 +45,7 @@ export const driver = {
   RemoteTraversal: rc.RemoteTraversal,
   ResponseError,
   DriverRemoteConnection,
+  LocalGraphConnection,
   Client,
   ResultSet,
   HttpRequest,
@@ -50,6 +53,7 @@ export const driver = {
     basic,
     sigv4,
   },
+  LocalExecutionError,
 };
 
 export const process = {
diff --git a/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
new file mode 100644
index 0000000000..99957284f2
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/language/executor/ExecuteVisitor.ts
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP } from '../../process/traversal.js';
+import type { Arg, Pipeline, StepDescriptor } from '../../process/local/types.js';
+
+/**
+ * Converts an ANTLR parse tree produced by GremlinParser into a StepDescriptor[]
+ * (Pipeline) suitable for execution by LocalExecutor.
+ *
+ * Uses the same className→visitXxx dispatch mechanism as TranslateVisitor but
+ * accumulates typed step descriptors rather than output strings.
+ */
+export class ExecuteVisitor {
+  private readonly steps: StepDescriptor[] = [];
+
+  getPipeline(): Pipeline {
+    return this.steps;
+  }
+
+  // ── Dispatch ──────────────────────────────────────────────────────────────
+
+  visit(tree: any): void {
+    if (tree == null) return;
+    const className: string = tree?.constructor?.name ?? '';
+    if (className.endsWith('Context')) {
+      const methodName = 'visit' + className.slice(0, -'Context'.length);
+      const method = (this as any)[methodName];
+      if (typeof method === 'function') {
+        method.call(this, tree);
+        return;
+      }
+    }
+    // Fall back to visiting children for unhandled intermediate nodes
+    this.visitChildren(tree);
+  }
+
+  private visitChildren(node: any): void {
+    const n = node.getChildCount?.() ?? 0;
+    for (let i = 0; i < n; i++) {
+      this.visit(node.getChild(i));
+    }
+  }
+
+  // ── Top-level structure ───────────────────────────────────────────────────
+
+  visitQueryList(ctx: any): void {
+    // queryList → query → rootTraversal | terminatedTraversal
+    const n = ctx.getChildCount();
+    for (let i = 0; i < n; i++) {
+      const child = ctx.getChild(i);
+      const name = child?.constructor?.name ?? '';
+      if (name === 'QueryContext') {
+        this.visit(child);
+        return;
+      }
+    }
+    this.visitChildren(ctx);
+  }
+
+  visitQuery(ctx: any): void {
+    this.visitChildren(ctx);
+  }
+
+  visitRootTraversal(ctx: any): void {
+    // traversalSource DOT traversalSourceSpawnMethod (DOT chainedTraversal)?
+    this.visit(ctx.traversalSourceSpawnMethod());
+    const chained = ctx.chainedTraversal?.();
+    if (chained) this.visitChainedTraversal(chained);
+  }
+
+  visitTerminatedTraversal(ctx: any): void {
+    this.visit(ctx.rootTraversal?.() ?? ctx.getChild(0));
+  }
+
+  visitChainedTraversal(ctx: any): void {
+    // Grammar is left-recursive: outermost node holds the LAST step, innermost holds the FIRST.
+    // Recurse into the left subtree first, then append the current (rightmost) step.
+    const next = ctx.chainedTraversal?.();
+    if (next) this.visitChainedTraversal(next);
+    this.visit(ctx.traversalMethod());
+  }
+
+  visitTraversalSourceSpawnMethod(ctx: any): void {
+    this.visitChildren(ctx);
+  }
+
+  visitTraversalMethod(ctx: any): void {
+    // Find the specific TraversalMethod_* child and dispatch to it.
+    // If no specific visitor exists, push a placeholder so validation can reject it.
+    const n = ctx.getChildCount?.() ?? 0;
+    for (let i = 0; i < n; i++) {
+      const child = ctx.getChild(i);
+      const name: string = child?.constructor?.name ?? '';
+      if (name.startsWith('TraversalMethod_') && name.endsWith('Context')) {
+        const visitorName = 'visit' + name.slice(0, -'Context'.length);
+        if (typeof (this as any)[visitorName] === 'function') {
+          (this as any)[visitorName](child);
+        } else {
+          // Unknown step — extract name (e.g. 'TraversalMethod_repeatContext' → 'repeat')
+          const raw = name.slice('TraversalMethod_'.length, -'Context'.length).split('_')[0];
+          this.push(raw, []);
+        }
+        return;
+      }
+    }
+  }
+
+  // ── Nested traversal → sub-pipeline ──────────────────────────────────────
+
+  private visitNestedTraversalAsSubPipeline(ctx: any): Pipeline {
+    const sub = new ExecuteVisitor();
+    // nestedTraversal has a chainedTraversal child
+    const chain = ctx.chainedTraversal?.();
+    if (chain) sub.visitChainedTraversal(chain);
+    return sub.getPipeline();
+  }
+
+  // ── Mid-traversal V/E (used inside anonymous traversals, e.g. __.V(1)) ──────
+
+  visitTraversalMethod_V(ctx: any): void {
+    const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+    this.push('V', args);
+  }
+
+  visitTraversalMethod_E(ctx: any): void {
+    const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+    this.push('E', args);
+  }
+
+  // ── Source spawn methods (g.V(), g.E(), g.addV(), g.addE()) ──────────────
+
+  visitTraversalSourceSpawnMethod_V(ctx: any): void {
+    const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+    this.push('V', args);
+  }
+
+  visitTraversalSourceSpawnMethod_E(ctx: any): void {
+    const args = this.recoverGenericVarargs(ctx.genericArgumentVarargs());
+    this.push('E', args);
+  }
+
+  visitTraversalSourceSpawnMethod_addV(ctx: any): void {
+    const strArg = ctx.stringArgument?.();
+    if (strArg) {
+      this.push('addV', [this.recoverStringArg(strArg)]);
+    } else {
+      this.push('addV', []);
+    }
+  }
+
+  visitTraversalSourceSpawnMethod_addE(ctx: any): void {
+    const strArg = ctx.stringArgument?.();
+    if (strArg) {
+      this.push('addE', [this.recoverStringArg(strArg)]);
+    } else {
+      // Traversal label form — not supported in Tiny Gremlin
+      this.push('addE(Traversal)', []);
+    }
+  }
+
+  // ── Navigation steps ──────────────────────────────────────────────────────
+
+  visitTraversalMethod_out(ctx: any): void {
+    this.push('out', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_in(ctx: any): void {
+    this.push('in', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_both(ctx: any): void {
+    this.push('both', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_outE(ctx: any): void {
+    this.push('outE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_inE(ctx: any): void {
+    this.push('inE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_bothE(ctx: any): void {
+    this.push('bothE', this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs()));
+  }
+
+  visitTraversalMethod_outV(ctx: any): void {
+    this.push('outV', []);
+  }
+
+  visitTraversalMethod_inV(ctx: any): void {
+    this.push('inV', []);
+  }
+
+  visitTraversalMethod_otherV(ctx: any): void {
+    this.push('otherV', []);
+  }
+
+  // ── Filter steps ──────────────────────────────────────────────────────────
+
+  // has(key) — existence check
+  visitTraversalMethod_has_String(ctx: any): void {
+    this.push('has', [this.recoverStringNullableLiteral(ctx.stringNullableLiteral())]);
+  }
+
+  // has(key, value)
+  visitTraversalMethod_has_String_Object(ctx: any): void {
+    this.push('has', [
+      this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+      this.recoverGenericArg(ctx.genericArgument()),
+    ]);
+  }
+
+  // has(key, P)
+  visitTraversalMethod_has_String_P(ctx: any): void {
+    this.push('has', [
+      this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+      this.recoverPredicate(ctx.traversalPredicate()),
+    ]);
+  }
+
+  // has(label, key, value) — label is stringNullableArgument, key is stringNullableLiteral
+  visitTraversalMethod_has_String_String_Object(ctx: any): void {
+    this.push('has', [
+      this.recoverStringNullableArgument(ctx.stringNullableArgument()),
+      this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+      this.recoverGenericArg(ctx.genericArgument()),
+    ]);
+  }
+
+  // has(label, key, P) — label is stringNullableArgument, key is stringNullableLiteral
+  visitTraversalMethod_has_String_String_P(ctx: any): void {
+    this.push('has', [
+      this.recoverStringNullableArgument(ctx.stringNullableArgument()),
+      this.recoverStringNullableLiteral(ctx.stringNullableLiteral()),
+      this.recoverPredicate(ctx.traversalPredicate()),
+    ]);
+  }
+
+  // has(T, value)
+  visitTraversalMethod_has_T_Object(ctx: any): void {
+    this.push('has', [
+      ctx.traversalT().getText(),
+      this.recoverGenericArg(ctx.genericArgument()),
+    ]);
+  }
+
+  // has(T, P)
+  visitTraversalMethod_has_T_P(ctx: any): void {
+    this.push('has', [
+      ctx.traversalT().getText(),
+      this.recoverPredicate(ctx.traversalPredicate()),
+    ]);
+  }
+
+  visitTraversalMethod_hasId_Object_Object(ctx: any): void {
+    const first = this.recoverGenericArg(ctx.genericArgument());
+    const rest = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+    this.push('hasId', [first, ...rest]);
+  }
+
+  visitTraversalMethod_hasId_P(ctx: any): void {
+    this.push('hasId', [this.recoverPredicate(ctx.traversalPredicate())]);
+  }
+
+  visitTraversalMethod_hasLabel_String_String(ctx: any): void {
+    const first = this.recoverStringNullableArgument(ctx.stringNullableArgument());
+    const rest = this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs?.());
+    this.push('hasLabel', [first, ...rest]);
+  }
+
+  visitTraversalMethod_hasLabel_P(ctx: any): void {
+    this.push('hasLabel', [this.recoverPredicate(ctx.traversalPredicate())]);
+  }
+
+  visitTraversalMethod_hasNot(ctx: any): void {
+    this.push('hasNot', [this.recoverStringNullableLiteral(ctx.stringNullableLiteral())]);
+  }
+
+  visitTraversalMethod_hasKey_String_String(ctx: any): void {
+    const first = this.recoverStringNullableArgument(ctx.stringNullableArgument());
+    const rest = this.recoverStringNullableVarargs(ctx.stringNullableArgumentVarargs?.());
+    this.push('hasKey', [first, ...rest]);
+  }
+
+  visitTraversalMethod_hasKey_P(ctx: any): void {
+    this.push('hasKey', [this.recoverPredicate(ctx.traversalPredicate())]);
+  }
+
+  visitTraversalMethod_hasValue_Object_Object(ctx: any): void {
+    const first = this.recoverGenericArg(ctx.genericArgument());
+    const rest = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+    this.push('hasValue', [first, ...rest]);
+  }
+
+  visitTraversalMethod_hasValue_P(ctx: any): void {
+    this.push('hasValue', [this.recoverPredicate(ctx.traversalPredicate())]);
+  }
+
+  // ── Value extraction steps ────────────────────────────────────────────────
+
+  visitTraversalMethod_id(ctx: any): void {
+    this.push('id', []);
+  }
+
+  visitTraversalMethod_label(ctx: any): void {
+    this.push('label', []);
+  }
+
+  visitTraversalMethod_key(ctx: any): void {
+    this.push('key', []);
+  }
+
+  visitTraversalMethod_value(ctx: any): void {
+    this.push('value', []);
+  }
+
+  visitTraversalMethod_values(ctx: any): void {
+    this.push('values', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+  }
+
+  visitTraversalMethod_valueMap(ctx: any): void {
+    // valueMap() or valueMap(key, key...)
+    const varargs = ctx.stringNullableArgumentVarargs?.();
+    this.push('valueMap', varargs ? this.recoverStringNullableVarargs(varargs) : []);
+  }
+
+  visitTraversalMethod_valueMap_String(ctx: any): void {
+    this.push('valueMap', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+  }
+
+  visitTraversalMethod_valueMap_boolean_String(ctx: any): void {
+    const includeTokens = ctx.booleanLiteral().getText() === 'true';
+    const keys = this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs?.());
+    // True sentinel at position 0 signals token inclusion to stepValueMap
+    this.push('valueMap', includeTokens ? [true, ...keys] : keys);
+  }
+
+  visitTraversalMethod_elementMap(ctx: any): void {
+    this.push('elementMap', this.recoverStringNullableLiteralVarargs(ctx.stringNullableLiteralVarargs()));
+  }
+
+  // ── Path step ─────────────────────────────────────────────────────────────
+
+  visitTraversalMethod_path(ctx: any): void {
+    this.push('path', []);
+  }
+
+  // ── Range steps ───────────────────────────────────────────────────────────
+
+  visitTraversalMethod_limit_long(ctx: any): void {
+    this.push('limit', [this.recoverIntegerArg(ctx.integerArgument())]);
+  }
+
+  visitTraversalMethod_limit_Scope_long(_ctx: any): void {
+    this.push('limit(Scope)', []);
+  }
+
+  visitTraversalMethod_range_long_long(ctx: any): void {
+    const args = ctx.integerArgument();
+    this.push('range', [this.recoverIntegerArg(args[0]), this.recoverIntegerArg(args[1])]);
+  }
+
+  visitTraversalMethod_range_Scope_long_long(_ctx: any): void {
+    this.push('range(Scope)', []);
+  }
+
+  visitTraversalMethod_skip_long(ctx: any): void {
+    this.push('skip', [this.recoverIntegerArg(ctx.integerArgument())]);
+  }
+
+  visitTraversalMethod_skip_Scope_long(_ctx: any): void {
+    this.push('skip(Scope)', []);
+  }
+
+  visitTraversalMethod_tail_Empty(ctx: any): void {
+    this.push('tail', [1]);
+  }
+
+  visitTraversalMethod_tail_long(ctx: any): void {
+    this.push('tail', [this.recoverIntegerArg(ctx.integerArgument())]);
+  }
+
+  visitTraversalMethod_tail_Scope(_ctx: any): void {
+    this.push('tail(Scope)', []);
+  }
+
+  visitTraversalMethod_tail_Scope_long(_ctx: any): void {
+    this.push('tail(Scope)', []);
+  }
+
+  // ── Ordering step and by() modulator ─────────────────────────────────────
+
+  visitTraversalMethod_order_Empty(ctx: any): void {
+    this.push('order', []);
+  }
+
+  visitTraversalMethod_order_Scope(_ctx: any): void {
+    this.push('order(Scope)', []);
+  }
+
+  /** by(String key) — sort by property value ascending */
+  visitTraversalMethod_by_String(ctx: any): void {
+    this.push('by', [this.recoverStringLiteral(ctx.stringLiteral()), 'asc']);
+  }
+
+  /** by(String key, Order) — sort by property value with direction */
+  visitTraversalMethod_by_String_Comparator(ctx: any): void {
+    const dir = ctx.traversalComparator().traversalOrder().getText();
+    this.push('by', [this.recoverStringLiteral(ctx.stringLiteral()), dir]);
+  }
+
+  /** by(T token) — sort by T.id or T.label */
+  visitTraversalMethod_by_T(ctx: any): void {
+    this.push('by', [ctx.traversalT().getText(), 'asc']);
+  }
+
+  /** by() — natural ascending sort */
+  visitTraversalMethod_by_Empty(_ctx: any): void {
+    this.push('by', [null, 'asc']);
+  }
+
+  /** by(Order) — natural sort with explicit direction */
+  visitTraversalMethod_by_Comparator(ctx: any): void {
+    const dir = ctx.traversalComparator().traversalOrder().getText();
+    this.push('by', [null, dir]);
+  }
+
+  /** by(Order) using traversalOrder directly */
+  visitTraversalMethod_by_Order(ctx: any): void {
+    const dir = ctx.traversalOrder().getText();
+    this.push('by', [null, dir]);
+  }
+
+  visitTraversalMethod_by_Traversal(_ctx: any): void { this.push('by(Traversal)', []); }
+  visitTraversalMethod_by_Traversal_Comparator(_ctx: any): void { this.push('by(Traversal)', []); }
+
+  /** by(T, Order) — T.id, T.label etc. with a direction, routed through traversalFunction */
+  visitTraversalMethod_by_Function(ctx: any): void {
+    const tCtx = ctx.traversalFunction().traversalT?.();
+    if (tCtx) {
+      this.push('by', [tCtx.getText(), 'asc']);
+    } else {
+      this.push('by(Function)', []);
+    }
+  }
+
+  visitTraversalMethod_by_Function_Comparator(ctx: any): void {
+    const tCtx = ctx.traversalFunction().traversalT?.();
+    if (tCtx) {
+      const dir = ctx.traversalComparator().traversalOrder().getText();
+      this.push('by', [tCtx.getText(), dir]);
+    } else {
+      this.push('by(Function)', []);
+    }
+  }
+
+  // ── Mutation steps ────────────────────────────────────────────────────────
+
+  visitTraversalMethod_addV_Empty(ctx: any): void {
+    this.push('addV', []);
+  }
+
+  visitTraversalMethod_addV_String(ctx: any): void {
+    this.push('addV', [this.recoverStringArg(ctx.stringArgument())]);
+  }
+
+  visitTraversalMethod_addV_Traversal(ctx: any): void {
+    this.push('addV', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+  }
+
+  visitTraversalMethod_addE_String(ctx: any): void {
+    this.push('addE', [this.recoverStringArg(ctx.stringArgument())]);
+  }
+
+  visitTraversalMethod_addE_Traversal(_ctx: any): void {
+    // addE(Traversal) is not supported in Tiny Gremlin — push an unsupported step name
+    // so the validation pass rejects it with a clear LocalExecutionError.
+    this.push('addE(Traversal)', []);
+  }
+
+  // property(key, value) — the supported form
+  visitTraversalMethod_property_Object_Object_Object(ctx: any): void {
+    this.push('property', [
+      this.recoverGenericLiteral(ctx.genericLiteral()),
+      this.recoverGenericArg(ctx.genericArgument()),
+    ]);
+  }
+
+  // from(traversal) modulator
+  visitTraversalMethod_from_Traversal(ctx: any): void {
+    this.push('from', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+  }
+
+  // from(string) modulator — label reference, treated as-is
+  visitTraversalMethod_from_String(ctx: any): void {
+    this.push('from', [this.recoverStringLiteral(ctx.stringLiteral())]);
+  }
+
+  // to(traversal) modulator
+  visitTraversalMethod_to_Traversal(ctx: any): void {
+    this.push('to', [this.visitNestedTraversalAsSubPipeline(ctx.nestedTraversal())]);
+  }
+
+  // to(string) modulator
+  visitTraversalMethod_to_String(ctx: any): void {
+    this.push('to', [this.recoverStringLiteral(ctx.stringLiteral())]);
+  }
+
+  // ── Argument recovery helpers ─────────────────────────────────────────────
+
+  private recoverStringLiteral(ctx: any): string {
+    if (ctx == null) return '';
+    const text: string = ctx.getText();
+    // Strip surrounding quotes (' or ") and unescape escaped quotes
+    if ((text.startsWith("'") && text.endsWith("'")) ||
+        (text.startsWith('"') && text.endsWith('"'))) {
+      return text.slice(1, -1)
+        .replace(/\\'/g, "'")
+        .replace(/\\"/g, '"')
+        .replace(/\\\\/g, '\\');
+    }
+    return text;
+  }
+
+  private recoverStringNullableLiteral(ctx: any): string | null {
+    if (ctx == null) return null;
+    const text: string = ctx.getText();
+    if (text === 'null') return null;
+    return this.recoverStringLiteral(ctx.stringLiteral?.() ?? ctx);
+  }
+
+  private recoverStringArg(ctx: any): string {
+    if (ctx == null) return '';
+    const lit = ctx.stringLiteral?.();
+    return lit ? this.recoverStringLiteral(lit) : ctx.getText();
+  }
+
+  private recoverStringNullableArgument(ctx: any): string | null {
+    if (ctx == null) return null;
+    const lit = ctx.stringNullableLiteral?.();
+    return lit ? this.recoverStringNullableLiteral(lit) : ctx.getText();
+  }
+
+  private recoverStringNullableVarargs(ctx: any): Arg[] {
+    if (ctx == null) return [];
+    const items = ctx.stringNullableArgument?.() ?? [];
+    return (Array.isArray(items) ? items : [items])
+      .map((a: any) => this.recoverStringNullableArgument(a));
+  }
+
+  /** Recover args from a stringNullableLiteralVarargs context (used by values, valueMap, elementMap). */
+  private recoverStringNullableLiteralVarargs(ctx: any): Arg[] {
+    if (ctx == null) return [];
+    const items = ctx.stringNullableLiteral?.() ?? [];
+    return (Array.isArray(items) ? items : [items])
+      .map((a: any) => this.recoverStringNullableLiteral(a));
+  }
+
+  private recoverIntegerArg(ctx: any): number {
+    if (ctx == null) return 0;
+    const lit = ctx.integerLiteral?.();
+    if (lit) return this.recoverIntegerLiteral(lit);
+    return parseInt(ctx.getText(), 10);
+  }
+
+  private recoverIntegerLiteral(ctx: any): number {
+    const text: string = ctx.getText().toLowerCase();
+    // Strip type suffix: L, S, B
+    const stripped = text.replace(/[lsb]$/, '');
+    return parseInt(stripped, 10);
+  }
+
+  private recoverFloatLiteral(ctx: any): number {
+    const text: string = ctx.getText().toLowerCase();
+    if (text === '+infinity') return Infinity;
+    if (text === '-infinity') return -Infinity;
+    if (text === 'nan') return NaN;
+    return parseFloat(text.replace(/[fd]$/, ''));
+  }
+
+  private recoverGenericLiteral(ctx: any): Arg {
+    if (ctx == null) return null;
+    const numeric = ctx.numericLiteral?.();
+    if (numeric) {
+      const int = numeric.integerLiteral?.();
+      if (int) return this.recoverIntegerLiteral(int);
+      const float = numeric.floatLiteral?.();
+      if (float) return this.recoverFloatLiteral(float);
+    }
+    const bool = ctx.booleanLiteral?.();
+    if (bool) return bool.getText() === 'true';
+    const str = ctx.stringLiteral?.();
+    if (str) return this.recoverStringLiteral(str);
+    const nil = ctx.nullLiteral?.();
+    if (nil) return null;
+    // Nested traversal as a generic literal
+    const nested = ctx.nestedTraversal?.();
+    if (nested) return this.visitNestedTraversalAsSubPipeline(nested);
+    // Collection literal [a, b, c]
+    const coll = ctx.genericCollectionLiteral?.();
+    if (coll) return coll.genericLiteral().map((gl: any) => this.recoverGenericLiteral(gl));
+    return ctx.getText();
+  }
+
+  private recoverGenericArg(ctx: any): Arg {
+    if (ctx == null) return null;
+    const lit = ctx.genericLiteral?.();
+    if (lit) return this.recoverGenericLiteral(lit);
+    return ctx.getText();
+  }
+
+  private recoverGenericVarargs(ctx: any): Arg[] {
+    if (ctx == null) return [];
+    const items = ctx.genericArgument?.() ?? [];
+    return (Array.isArray(items) ? items : [items])
+      .map((a: any) => this.recoverGenericArg(a));
+  }
+
+  private recoverPredicate(ctx: any): P | TextP {
+    if (ctx == null) return P.eq(null);
+    const name: string = ctx.constructor?.name ?? '';
+
+    // Compound predicates: predicate DOT and/or LPAREN predicate RPAREN (6 children)
+    if (ctx.getChildCount?.() === 6) {
+      const op = ctx.getChild(2)?.getText();
+      if (op === 'and' || op === 'or') {
+        const left = this.recoverPredicate(ctx.getChild(0));
+        const right = this.recoverPredicate(ctx.getChild(4));
+        return op === 'and' ? left.and(right) : left.or(right);
+      }
+    }
+
+    // TextP string predicates — checked BEFORE 'not' to avoid prefix collision
+    if (name.includes('Predicate_notContaining'))
+      return TextP.notContaining(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_notStartingWith'))
+      return TextP.notStartingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_notEndingWith'))
+      return TextP.notEndingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_notRegex'))
+      return (TextP as any).notRegex(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_containing'))
+      return TextP.containing(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_startingWith'))
+      return TextP.startingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_endingWith'))
+      return TextP.endingWith(this.recoverStringArg(ctx.stringArgument()) as string);
+    if (name.includes('Predicate_regex'))
+      return (TextP as any).regex(this.recoverStringArg(ctx.stringArgument()) as string);
+    // P numeric predicates
+    if (name.includes('Predicate_eq')) return P.eq(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_neq')) return P.neq(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_lte')) return P.lte(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_lt')) return P.lt(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_gte')) return P.gte(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_gt')) return P.gt(this.recoverGenericArg(ctx.genericArgument()));
+    if (name.includes('Predicate_between')) {
+      const args = ctx.genericArgument();
+      return P.between(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+    }
+    if (name.includes('Predicate_within')) {
+      const vals = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+      return (P as any).within(...vals);
+    }
+    if (name.includes('Predicate_without')) {
+      const vals = this.recoverGenericVarargs(ctx.genericArgumentVarargs?.());
+      return (P as any).without(...vals);
+    }
+    if (name.includes('Predicate_inside')) {
+      const args = ctx.genericArgument();
+      return (P as any).inside(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+    }
+    if (name.includes('Predicate_outside')) {
+      const args = ctx.genericArgument();
+      return (P as any).outside(this.recoverGenericArg(args[0]), this.recoverGenericArg(args[1]));
+    }
+    if (name.includes('Predicate_not')) return P.not(this.recoverPredicate(ctx.traversalPredicate()));
+
+    // Single-child wrapper: visit the child
+    if (ctx.getChildCount?.() === 1) return this.recoverPredicate(ctx.getChild(0));
+
+    return P.eq(null);
+  }
+
+  // ── Step accumulator ──────────────────────────────────────────────────────
+
+  private push(name: string, args: Arg[]): void {
+    this.steps.push({ name, args });
+  }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts b/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
index 5920c48dc9..699e2fc03b 100644
--- a/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/anonymous-traversal.ts
@@ -18,6 +18,7 @@
  */
 
 import { RemoteConnection, RemoteStrategy } from '../driver/remote-connection.js';
+import { LocalGraphConnection } from '../driver/local-graph-connection.js';
 import { Graph } from '../structure/graph.js';
 import { GraphTraversalSource, GraphTraversal } from './graph-traversal.js';
 import { TraversalStrategies } from './traversal-strategy.js';
@@ -51,12 +52,16 @@ export default class AnonymousTraversalSource {
   }
 
   /**
-   * Creates a {@link GraphTraversalSource} binding a {@link RemoteConnection} to a remote {@link Graph} instances as its
-   * reference so that traversals spawned from it will execute over that reference.
-   * @param {RemoteConnection} connection
+   * Creates a {@link GraphTraversalSource} bound to a connection or a local {@link Graph}.
+   * When passed a {@link Graph} instance, execution runs locally via Tiny Gremlin.
+   * When passed a {@link RemoteConnection}, traversals are submitted to a remote server.
+   * @param {RemoteConnection | Graph} connectionOrGraph
    * @return {GraphTraversalSource}
    */
-  with_(connection: RemoteConnection) {
+  with_(connectionOrGraph: RemoteConnection | Graph) {
+    const connection = connectionOrGraph instanceof Graph
+      ? new LocalGraphConnection(connectionOrGraph)
+      : connectionOrGraph;
     const traversalStrategies = new TraversalStrategies();
     traversalStrategies.addStrategy(new RemoteStrategy(connection));
     const gl = new GremlinLang();
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts
new file mode 100644
index 0000000000..9658c34249
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutionError.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Thrown when a Gremlin traversal contains a step not supported by the
+ * Tiny Gremlin local execution subset.
+ */
+export class LocalExecutionError extends Error {
+  constructor(message: string) {
+    super(message);
+    this.name = 'LocalExecutionError';
+  }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
new file mode 100644
index 0000000000..ee4c04dc63
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/LocalExecutor.ts
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Arg, Pipeline, StepDescriptor } from './types.js';
+import { defaultPasses, type OptimizationPass } from './passes.js';
+import { LocalExecutionError } from './LocalExecutionError.js';
+import { Graph, Vertex, Edge } from '../../structure/graph.js';
+import {
+  wrap, getValue, type StreamItem,
+  stepOut, stepIn, stepBoth, stepOutE, stepInE, stepBothE, stepOutV, stepInV, stepOtherV,
+  stepHas, stepHasId, stepHasLabel, stepHasNot,
+  stepId, stepLabel, stepValue, stepKey, stepValues, stepValueMap, stepElementMap,
+  stepPath,
+  stepLimit, stepRange, stepSkip, stepTail, stepOrder,
+  stepAddV, stepAddEWithModulators, stepProperty,
+} from './steps.js';
+
+type StepFn = (source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean) => Generator<StreamItem>;
+
+/**
+ * Executes a Tiny Gremlin Pipeline against a local in-memory Graph, returning
+ * an AsyncGenerator that yields one result element at a time.
+ */
+export class LocalExecutor {
+  private readonly passes: OptimizationPass[];
+
+  constructor(passes: OptimizationPass[] = defaultPasses) {
+    this.passes = passes;
+  }
+
+  execute(pipeline: Pipeline, graph: Graph): AsyncGenerator<any> {
+    const optimized = this.passes.reduce((p, pass) => pass(p), pipeline);
+    const folded = this.foldModulators(optimized);
+    const trackPaths = folded.some(s => s.name === 'path' || s.name === 'otherV');
+    const stream = this.buildChain(folded, graph, trackPaths);
+    return this.toAsync(stream, trackPaths);
+  }
+
+  /**
+   * Folds 'from' and 'to' modulator steps that follow an 'addE' step into a
+   * composite descriptor so the addE executor has all modulator args available.
+   */
+  private foldModulators(pipeline: Pipeline): Pipeline {
+    const result: StepDescriptor[] = [];
+    let i = 0;
+    while (i < pipeline.length) {
+      const step = pipeline[i];
+      if (step.name === 'addE') {
+        let fromArgs: Arg[] | null = null;
+        let toArgs: Arg[] | null = null;
+        let j = i + 1;
+        while (j < pipeline.length && (pipeline[j].name === 'from' || pipeline[j].name === 'to')) {
+          if (pipeline[j].name === 'from') fromArgs = pipeline[j].args[0] as unknown as Arg[];
+          else toArgs = pipeline[j].args[0] as unknown as Arg[];
+          j++;
+        }
+        result.push({ name: 'addE', args: [step.args[0], fromArgs as unknown as Arg, toArgs as unknown as Arg] });
+        i = j;
+      } else if (step.name === 'order') {
+        // Fold a single following by() modulator into the order step args
+        const next = pipeline[i + 1];
+        if (next?.name === 'by') {
+          result.push({ name: 'order', args: next.args }); // args = [key, direction]
+          i += 2;
+        } else {
+          result.push(step);
+          i++;
+        }
+      } else {
+        result.push(step);
+        i++;
+      }
+    }
+    return result;
+  }
+
+  private buildChain(pipeline: Pipeline, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+    if (pipeline.length === 0) return [];
+
+    const [source, ...rest] = pipeline;
+    let stream: Iterable<StreamItem> = this.applySourceStep(source, graph, trackPaths);
+
+    for (const step of rest) {
+      stream = this.applyStep(step, stream, graph, trackPaths);
+    }
+    return stream;
+  }
+
+  private applySourceStep(step: StepDescriptor, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+    switch (step.name) {
+      case 'V': return sourceV(step.args, graph, trackPaths);
+      case 'E': return sourceE(step.args, graph, trackPaths);
+      case 'addV': return stepAddV([null], step.args, graph, trackPaths);
+      case 'addE': return this.applyAddE([null], step, graph, trackPaths);
+      default:
+        throw new LocalExecutionError(`'${step.name}' cannot be used as a source step`);
+    }
+  }
+
+  private applyStep(step: StepDescriptor, source: Iterable<StreamItem>, graph: Graph, trackPaths: boolean): Iterable<StreamItem> {
+    if (step.name === 'addE') return this.applyAddE(source, step, graph, trackPaths);
+    const fn = STEP_REGISTRY.get(step.name);
+    if (!fn) throw new LocalExecutionError(`No implementation for step '${step.name}'`);
+    return fn(source, step.args, graph, trackPaths);
+  }
+
+  private applyAddE(
+    source: Iterable<StreamItem>,
+    step: StepDescriptor,
+    graph: Graph,
+    trackPaths: boolean,
+  ): Iterable<StreamItem> {
+    const label = step.args[0] as string;
+    const fromPipeline = step.args[1] as Arg[] | null;
+    const toPipeline = step.args[2] as Arg[] | null;
+    return stepAddEWithModulators(
+      source, label, fromPipeline, toPipeline, graph, trackPaths,
+      (subPipeline, g) => this.executeSubPipeline(subPipeline as unknown as Pipeline, g),
+    );
+  }
+
+  private executeSubPipeline(pipeline: Pipeline, graph: Graph): any {
+    const stream = this.buildChain(pipeline, graph, false);
+    for (const item of stream) return item;
+    return null;
+  }
+
+  private async *toAsync(stream: Iterable<StreamItem>, trackPaths: boolean): AsyncGenerator<any> {
+    for (const item of stream) {
+      const value = trackPaths ? getValue(item, true) : item;
+      // Yield a shallow copy of Vertex/Edge so callers that mutate
+      // result objects (e.g. removeProperties) don't corrupt the graph.
+      yield shallowCopyElement(value);
+    }
+  }
+}
+
+// ── Result copying ────────────────────────────────────────────────────────────
+
+/** Shallow-copies Vertex/Edge/Path so callers can mutate results without corrupting the graph. */
+function shallowCopyElement(value: any): any {
+  if (value instanceof Vertex || value instanceof Edge) return Object.assign(Object.create(Object.getPrototypeOf(value)), value);
+  return value;
+}
+
+// ── Source steps ──────────────────────────────────────────────────────────────
+// Mid-traversal V/E: for each incoming traverser, emit matching vertices/edges (cross-product).
+
+function* midSourceV(source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+  for (const _item of source) {
+    yield* sourceV(args, graph, trackPaths);
+  }
+}
+
+function* midSourceE(source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+  for (const _item of source) {
+    yield* sourceE(args, graph, trackPaths);
+  }
+}
+
+function lookupById<T>(map: Map<any, T>, id: Arg): T | undefined {
+  return map.get(id);
+}
+
+function flattenIds(args: Arg[]): Arg[] {
+  // Single list argument: unroll it, consistent with V([1,2,3]) semantics.
+  // Multiple arguments: treat each as-is — a list is a single ID value, not unrolled.
+  if (args.length === 1 && Array.isArray(args[0])) return args[0] as unknown as Arg[];
+  return args;
+}
+
+function* sourceV(args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+  const ids = flattenIds(args);
+  if (ids.length === 0) {
+    for (const v of graph.vertices.values()) yield wrap(v, [], trackPaths);
+  } else {
+    for (const id of ids) {
+      if (id === null || id === undefined) continue;
+      const v = lookupById(graph.vertices, id);
+      if (v !== undefined) yield wrap(v, [], trackPaths);
+    }
+  }
+}
+
+function* sourceE(args: Arg[], graph: Graph, trackPaths: boolean): Generator<StreamItem> {
+  const ids = flattenIds(args);
+  if (ids.length === 0) {
+    for (const e of graph.edges.values()) yield wrap(e, [], trackPaths);
+  } else {
+    for (const id of ids) {
+      if (id === null || id === undefined) continue;
+      const e = lookupById(graph.edges, id);
+      if (e !== undefined) yield wrap(e, [], trackPaths);
+    }
+  }
+}
+
+// ── Step dispatch table ───────────────────────────────────────────────────────
+
+const STEP_REGISTRY = new Map<string, StepFn>([
+  // Mid-traversal V/E: for each incoming element produce matching vertices/edges (cross-product)
+  ['V',           (source, args, graph, tp) => midSourceV(source, args, graph, tp)],
+  ['E',           (source, args, graph, tp) => midSourceE(source, args, graph, tp)],
+  ['out',         stepOut],
+  ['in',          stepIn],
+  ['both',        stepBoth],
+  ['outE',        stepOutE],
+  ['inE',         stepInE],
+  ['bothE',       stepBothE],
+  ['outV',        stepOutV],
+  ['inV',         stepInV],
+  ['otherV',      stepOtherV],
+  ['has',         stepHas],
+  ['hasId',       stepHasId],
+  ['hasLabel',    stepHasLabel],
+  ['hasNot',      stepHasNot],
+  ['id',          stepId],
+  ['label',       stepLabel],
+  ['value',       stepValue],
+  ['key',         stepKey],
+  ['values',      stepValues],
+  ['valueMap',    stepValueMap],
+  ['elementMap',  stepElementMap],
+  ['path',        stepPath],
+  ['limit',       stepLimit],
+  ['range',       stepRange],
+  ['skip',        stepSkip],
+  ['tail',        stepTail],
+  ['order',       stepOrder],
+  ['addV',        stepAddV],
+  ['property',    stepProperty],
+]);
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/passes.ts b/gremlin-js/gremlin-javascript/lib/process/local/passes.ts
new file mode 100644
index 0000000000..ce7635263a
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/passes.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { Arg, Pipeline, StepDescriptor } from './types.js';
+import { LocalExecutionError } from './LocalExecutionError.js';
+
+export type OptimizationPass = (pipeline: Pipeline) => Pipeline;
+
+/** Steps supported by the Tiny Gremlin local executor. */
+const SUPPORTED_STEPS = new Set<string>([
+  'V', 'E',
+  'out', 'in', 'both', 'outE', 'inE', 'bothE', 'outV', 'inV', 'otherV',
+  'has', 'hasId', 'hasLabel', 'hasNot',
+  'id', 'label', 'values', 'valueMap', 'elementMap', 'value', 'key',
+  'path',
+  'limit', 'range', 'skip', 'tail',
+  'order', 'by',
+  'addV', 'addE', 'property',
+  'from', 'to',
+]);
+
+/**
+ * Validation pass — runs first. Throws LocalExecutionError for any step not
+ * in the Tiny Gremlin subset, including steps inside nested sub-pipelines.
+ */
+export function validationPass(pipeline: Pipeline): Pipeline {
+  for (const step of pipeline) {
+    if (!SUPPORTED_STEPS.has(step.name)) {
+      throw new LocalExecutionError(
+        `'${step.name}' is not supported for Tiny Gremlin local execution`,
+      );
+    }
+    for (const arg of step.args) {
+      if (Array.isArray(arg) && arg.length > 0 && arg[0] !== null && typeof arg[0] === 'object' && 'name' in arg[0]) {
+        validationPass(arg as Pipeline);
+      }
+    }
+  }
+  return pipeline;
+}
+
+/**
+ * HasId folding pass — collapses V().hasId(id) → V(id) and E().hasId(id) → E(id),
+ * enabling O(1) direct map lookup instead of full iteration.
+ */
+export function hasIdFoldingPass(pipeline: Pipeline): Pipeline {
+  const result: StepDescriptor[] = [];
+  let i = 0;
+  while (i < pipeline.length) {
+    const step = pipeline[i];
+    const next = pipeline[i + 1];
+    if (
+      (step.name === 'V' || step.name === 'E') &&
+      step.args.length === 0 &&
+      next?.name === 'hasId'
+    ) {
+      result.push({ name: step.name, args: next.args });
+      i += 2;
+    } else {
+      result.push(step);
+      i++;
+    }
+  }
+  return result;
+}
+
+/** Default optimization passes applied before execution. */
+export const defaultPasses: OptimizationPass[] = [
+  validationPass,
+  hasIdFoldingPass,
+];
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/steps.ts b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
new file mode 100644
index 0000000000..7ae0700683
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/steps.ts
@@ -0,0 +1,730 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP, t, direction } from '../traversal.js';
+import { Graph, Vertex, Edge, VertexProperty, Property, Path } from '../../structure/graph.js';
+import type { Arg } from './types.js';
+
+// ── Traverser helpers ─────────────────────────────────────────────────────────
+
+export type StreamItem = any;
+
+export function wrap(value: any, prevPath: PathEntry[], trackPaths: boolean): StreamItem {
+  if (!trackPaths) return value;
+  return { value, path: [...prevPath, { labels: [], object: value }] };
+}
+
+export function getValue(item: StreamItem, trackPaths: boolean): any {
+  return trackPaths ? item.value : item;
+}
+
+export function getPath(item: StreamItem, trackPaths: boolean): PathEntry[] {
+  return trackPaths ? (item.path ?? []) : [];
+}
+
+export interface PathEntry {
+  labels: string[];
+  object: any;
+}
+
+// ── Property lookup helpers ───────────────────────────────────────────────────
+
+/** Returns the property key regardless of whether it's a Property or VertexProperty. */
+function propKey(p: Property | VertexProperty): string {
+  return p instanceof VertexProperty ? p.label : p.key;
+}
+
+/** Returns all properties of an element as a flat list. */
+function allProps(el: Vertex | Edge): Array<Property | VertexProperty> {
+  return (el.properties ?? []) as Array<Property | VertexProperty>;
+}
+
+/** Returns all values for a given key on an element (vertex properties are multi-valued). */
+function getValues(el: Vertex | Edge, key: string): any[] {
+  return allProps(el)
+    .filter(p => propKey(p) === key)
+    .map(p => (p as any).value);
+}
+
+/** Returns true when the element has at least one property with the given key. */
+function hasKey(el: Vertex | Edge, key: string): boolean {
+  return allProps(el).some(p => propKey(p) === key);
+}
+
+// ── Predicate evaluation ──────────────────────────────────────────────────────
+
+export function evaluatePredicate(pred: P | TextP, value: any): boolean {
+  const op = pred.operator;
+  const v = pred.value;
+  const other = pred.other;
+
+  switch (op) {
+    case 'eq':  { const c = compareForPredicate(value, v); return c !== null && c === 0; }
+    case 'neq': { const c = compareForPredicate(value, v); return c !== null ? c !== 0 : value !== v; }
+    case 'lt':  { const c = compareForPredicate(value, v); return c !== null && c < 0; }
+    case 'lte': { const c = compareForPredicate(value, v); return c !== null && c <= 0; }
+    case 'gt':  { const c = compareForPredicate(value, v); return c !== null && c > 0; }
+    case 'gte': { const c = compareForPredicate(value, v); return c !== null && c >= 0; }
+    case 'between': {
+      const clo = compareForPredicate(value, v);
+      const chi = compareForPredicate(value, other);
+      return clo !== null && chi !== null && clo >= 0 && chi < 0;
+    }
+    case 'inside': {
+      const clo = compareForPredicate(value, v);
+      const chi = compareForPredicate(value, other);
+      return clo !== null && chi !== null && clo > 0 && chi < 0;
+    }
+    case 'outside': {
+      const clo = compareForPredicate(value, v);
+      const chi = compareForPredicate(value, other);
+      return (clo !== null && clo < 0) || (chi !== null && chi > 0);
+    }
+    case 'within':  return (Array.isArray(v) ? v : [v]).includes(value);
+    case 'without': return !(Array.isArray(v) ? v : [v]).includes(value);
+    case 'not':     return !evaluatePredicate(v as P, value);
+    case 'and':     return evaluatePredicate(v as P, value) && evaluatePredicate(other as P, value);
+    case 'or':      return evaluatePredicate(v as P, value) || evaluatePredicate(other as P, value);
+    case 'containing':       return typeof value === 'string' && value.includes(v as string);
+    case 'notContaining':    return typeof value === 'string' && !value.includes(v as string);
+    case 'startingWith':     return typeof value === 'string' && value.startsWith(v as string);
+    case 'notStartingWith':  return typeof value === 'string' && !value.startsWith(v as string);
+    case 'endingWith':       return typeof value === 'string' && value.endsWith(v as string);
+    case 'notEndingWith':    return typeof value === 'string' && !value.endsWith(v as string);
+    case 'regex':    return typeof value === 'string' && new RegExp(v as string, 'u').test(value);
+    case 'notRegex': return typeof value === 'string' && !new RegExp(v as string, 'u').test(value);
+    default: return false;
+  }
+}
+
+// ── Navigation steps ──────────────────────────────────────────────────────────
+
+export function* stepOut(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const labels = args as string[];
+  for (const item of source) {
+    const v: Vertex = getValue(item, trackPaths);
+    const edges = graph.outEdges.get(v.id) ?? [];
+    for (const e of edges) {
+      if (labels.length === 0 || labels.includes(e.label)) {
+        yield wrap(e.inV, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepIn(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const labels = args as string[];
+  for (const item of source) {
+    const v: Vertex = getValue(item, trackPaths);
+    const edges = graph.inEdges.get(v.id) ?? [];
+    for (const e of edges) {
+      if (labels.length === 0 || labels.includes(e.label)) {
+        yield wrap(e.outV, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepBoth(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const buf = [...source];
+  yield* stepOut(buf, args, graph, trackPaths);
+  yield* stepIn(buf, args, graph, trackPaths);
+}
+
+export function* stepOutE(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const labels = args as string[];
+  for (const item of source) {
+    const v: Vertex = getValue(item, trackPaths);
+    const edges = graph.outEdges.get(v.id) ?? [];
+    for (const e of edges) {
+      if (labels.length === 0 || labels.includes(e.label)) {
+        yield wrap(e, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepInE(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const labels = args as string[];
+  for (const item of source) {
+    const v: Vertex = getValue(item, trackPaths);
+    const edges = graph.inEdges.get(v.id) ?? [];
+    for (const e of edges) {
+      if (labels.length === 0 || labels.includes(e.label)) {
+        yield wrap(e, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepBothE(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const buf = [...source];  // already buffered correctly
+  yield* stepOutE(buf, args, graph, trackPaths);
+  yield* stepInE(buf, args, graph, trackPaths);
+}
+
+export function* stepOutV(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const e: Edge = getValue(item, trackPaths);
+    yield wrap(e.outV, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepInV(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const e: Edge = getValue(item, trackPaths);
+    yield wrap(e.inV, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepOtherV(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  // trackPaths is always true when otherV is present (forced in LocalExecutor)
+  for (const item of source) {
+    const e: Edge = getValue(item, trackPaths);
+    const path = getPath(item, trackPaths);
+    const prevEntry = path.length >= 2 ? path[path.length - 2] : null;
+    const prevId = prevEntry?.object?.id;
+    const result = prevId === e.outV.id ? e.inV : e.outV;
+    yield wrap(result, path, trackPaths);
+  }
+}
+
+// ── Filter steps ──────────────────────────────────────────────────────────────
+
+export function* stepHas(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    if (matchesHas(el, args)) yield item;
+  }
+}
+
+function matchesHas(el: any, args: Arg[]): boolean {
+  if (!(el instanceof Vertex || el instanceof Edge)) return false;
+
+  if (args.length === 1) {
+    const key = args[0] as string;
+    if (key === 'T.id' || key === 'id') return true;
+    if (key === 'T.label' || key === 'label') return true;
+    return hasKey(el, key);
+  }
+
+  if (args.length === 2) {
+    const [key, valOrPred] = args;
+    const keyStr = key as string;
+    let values: any[];
+    if (keyStr === 'T.id' || keyStr === 'id') {
+      values = [el.id];
+    } else if (keyStr === 'T.label' || keyStr === 'label') {
+      values = [el.label];
+    } else {
+      values = getValues(el, keyStr);
+    }
+    if (values.length === 0) return false;
+    if (valOrPred instanceof P || valOrPred instanceof TextP) {
+      return values.some(v => evaluatePredicate(valOrPred, v));
+    }
+    return values.some(v => v === valOrPred);
+  }
+
+  if (args.length === 3) {
+    const [label, key, valOrPred] = args;
+    if (el.label !== label) return false;
+    const values = getValues(el, key as string);
+    if (values.length === 0) return false;
+    if (valOrPred instanceof P || valOrPred instanceof TextP) {
+      return values.some(v => evaluatePredicate(valOrPred as P, v));
+    }
+    return values.some(v => v === valOrPred);
+  }
+
+  return false;
+}
+
+function idMatches(id: any, argId: Arg): boolean {
+  if (argId === null || argId === undefined) return false;
+  return id === argId;
+}
+
+function flattenIdArgs(args: Arg[]): Arg[] {
+  // Single list argument: unroll it, consistent with hasId([1,2,3]) semantics.
+  // Multiple arguments: treat each as-is — a list is a single ID value, not unrolled.
+  if (args.length === 1 && Array.isArray(args[0])) return args[0] as unknown as Arg[];
+  return args;
+}
+
+export function* stepHasId(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const flatArgs = flattenIdArgs(args);
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const id = el?.id;
+    if (flatArgs.length > 0 && flatArgs[0] instanceof P) {
+      if (evaluatePredicate(flatArgs[0] as P, id)) yield item;
+    } else if (flatArgs.some(a => idMatches(id, a))) {
+      yield item;
+    }
+  }
+}
+
+export function* stepHasLabel(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const label = el?.label;
+    if (args.length > 0 && args[0] instanceof P) {
+      if (evaluatePredicate(args[0] as P, label)) yield item;
+    } else if (args.length === 0 || args.includes(label)) {
+      yield item;
+    }
+  }
+}
+
+export function* stepHasNot(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const key = args[0] as string;
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    if (el instanceof Vertex || el instanceof Edge) {
+      if (!hasKey(el, key)) yield item;
+    }
+  }
+}
+
+export function* stepHasKey(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const p = getValue(item, trackPaths);
+    const k = propKey(p);
+    if (args.length === 0) {
+      yield item;
+    } else if (args[0] instanceof P) {
+      if (evaluatePredicate(args[0] as P, k)) yield item;
+    } else if (args.includes(k)) {
+      yield item;
+    }
+  }
+}
+
+export function* stepHasValue(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const p = getValue(item, trackPaths);
+    const v = p?.value;
+    if (args.length === 0) {
+      yield item;
+    } else if (args[0] instanceof P) {
+      if (evaluatePredicate(args[0] as P, v)) yield item;
+    } else if (args.some(a => a == v)) {
+      yield item;
+    }
+  }
+}
+
+// ── Value extraction steps ────────────────────────────────────────────────────
+
+export function* stepId(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    yield wrap(el?.id, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepLabel(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    yield wrap(el?.label, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepValue(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const p = getValue(item, trackPaths);
+    yield wrap(p?.value, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepKey(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const p = getValue(item, trackPaths);
+    yield wrap(propKey(p), getPath(item, trackPaths), trackPaths);
+  }
+}
+
+export function* stepValues(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const keys = args as string[];
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const props = allProps(el);
+    for (const p of props) {
+      if (keys.length === 0 || keys.includes(propKey(p))) {
+        yield wrap((p as any).value, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepProperties(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const keys = args as string[];
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const props = allProps(el);
+    for (const p of props) {
+      if (keys.length === 0 || keys.includes(propKey(p))) {
+        yield wrap(p, getPath(item, trackPaths), trackPaths);
+      }
+    }
+  }
+}
+
+export function* stepValueMap(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  // A boolean true as the first arg means includeTokens (valueMap(true, ...))
+  const includeTokens = args.length > 0 && args[0] === true;
+  const keys = (includeTokens ? args.slice(1) : args) as string[];
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const map = new Map<any, any[]>();
+    if (includeTokens) {
+      map.set(t.id, el?.id);
+      map.set(t.label, el?.label);
+    }
+    for (const p of allProps(el)) {
+      const k = propKey(p);
+      if (keys.length === 0 || keys.includes(k)) {
+        if (!map.has(k)) map.set(k, []);
+        map.get(k)!.push((p as any).value);
+      }
+    }
+    yield wrap(map, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+/** Builds the stub vertex map used as Direction.OUT / Direction.IN values in edge elementMap. */
+function vertexStubMap(v: Vertex): Map<any, any> {
+  const m = new Map<any, any>();
+  m.set(t.id, v.id);
+  m.set(t.label, v.label);
+  return m;
+}
+
+export function* stepElementMap(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const keys = args as string[];
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    const map = new Map<any, any>();
+    map.set(t.id, el?.id);
+    map.set(t.label, el?.label);
+    if (el instanceof Edge) {
+      // Standard Gremlin edge elementMap includes Direction.OUT and Direction.IN stub maps
+      map.set(direction.out, vertexStubMap(el.outV));
+      map.set(direction.in, vertexStubMap(el.inV));
+    }
+    for (const p of allProps(el)) {
+      const k = propKey(p);
+      if (keys.length === 0 || keys.includes(k)) {
+        map.set(k, (p as any).value);
+      }
+    }
+    yield wrap(map, getPath(item, trackPaths), trackPaths);
+  }
+}
+
+// ── Path step ─────────────────────────────────────────────────────────────────
+
+export function* stepPath(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  for (const item of source) {
+    const pathEntries: PathEntry[] = getPath(item, trackPaths);
+    const labels: string[][] = pathEntries.map(e => e.labels);
+    const objects: any[] = pathEntries.map(e => e.object);
+    const pathObj = new Path(labels, objects);
+    // Yield as a traverser so toAsync's trackPaths unwrap produces the Path value correctly.
+    yield trackPaths ? { value: pathObj, path: [] } : pathObj;
+  }
+}
+
+// ── Range steps ───────────────────────────────────────────────────────────────
+
+export function* stepLimit(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const n = args[0] as number;
+  let count = 0;
+  for (const item of source) {
+    if (count >= n) return;
+    yield item;
+    count++;
+  }
+}
+
+export function* stepRange(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const lo = args[0] as number;
+  const hi = args[1] as number;
+  if (lo > hi) throw new Error(`Not a legal range: [${lo}, ${hi}]`);
+  let i = 0;
+  for (const item of source) {
+    if (i >= hi) return;
+    if (i >= lo) yield item;
+    i++;
+  }
+}
+
+export function* stepSkip(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const n = args[0] as number;
+  let i = 0;
+  for (const item of source) {
+    if (i++ >= n) yield item;
+  }
+}
+
+export function* stepTail(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const n = args[0] as number;
+  const buf: StreamItem[] = [];
+  for (const item of source) buf.push(item);
+  yield* buf.slice(Math.max(0, buf.length - n));
+}
+
+// ── Gremlin Orderability semantics ───────────────────────────────────────────
+// Total order across all types per the priority list in gremlin-semantics.asciidoc.
+
+function gremlinTypeOrder(v: any): number {
+  if (v === null || v === undefined) return 1;          // nulltype
+  if (typeof v === 'boolean') return 2;                 // Boolean
+  if (typeof v === 'number' || typeof v === 'bigint') return 3; // Number
+  if (v instanceof Date) return 4;                      // Date
+  if (typeof v === 'string') return 5;                  // String
+  if (v instanceof Vertex) return 7;                    // Vertex
+  if (v instanceof Edge) return 8;                      // Edge
+  if (v instanceof VertexProperty) return 9;            // VertexProperty
+  if (v instanceof Property) return 10;                 // Property
+  if (v instanceof Path) return 11;                     // Path
+  if (v instanceof Set) return 12;                      // Set
+  if (Array.isArray(v)) return 13;                      // List
+  if (v instanceof Map) return 14;                      // Map
+  return 15;                                            // Unknown
+}
+
+function compareNumeric(a: number, b: number): number {
+  // Per orderability: NaN > +Infinity > everything else
+  const aNaN = Number.isNaN(a), bNaN = Number.isNaN(b);
+  if (aNaN && bNaN) return 0;
+  if (aNaN) return 1;
+  if (bNaN) return -1;
+  return a < b ? -1 : a > b ? 1 : 0;
+}
+
+function compareValues(va: any, vb: any): number {
+  const ta = gremlinTypeOrder(va);
+  const tb = gremlinTypeOrder(vb);
+  if (ta !== tb) return ta - tb;
+
+  // Within same type family:
+  if (va === null || va === undefined) return 0;
+  if (typeof va === 'boolean') return (va ? 1 : 0) - (vb ? 1 : 0);
+  if (typeof va === 'number') return compareNumeric(va as number, vb as number);
+  if (typeof va === 'bigint') return va < vb ? -1 : va > vb ? 1 : 0;
+  if (va instanceof Date) return va.getTime() - (vb as Date).getTime();
+  if (typeof va === 'string') return va < vb ? -1 : va > vb ? 1 : 0;
+  // Elements: compare by id
+  if (va instanceof Vertex || va instanceof Edge || va instanceof VertexProperty) {
+    return compareValues((va as any).id, (vb as any).id);
+  }
+  if (va instanceof Property) {
+    const kc = compareValues((va as Property).key, (vb as Property).key);
+    return kc !== 0 ? kc : compareValues((va as Property).value, (vb as Property).value);
+  }
+  if (Array.isArray(va)) {
+    const len = Math.min(va.length, (vb as any[]).length);
+    for (let i = 0; i < len; i++) {
+      const c = compareValues(va[i], (vb as any[])[i]);
+      if (c !== 0) return c;
+    }
+    return va.length - (vb as any[]).length;
+  }
+  // Unknown: class name then toString()
+  const sa = Object.prototype.toString.call(va);
+  const sb = Object.prototype.toString.call(vb);
+  if (sa !== sb) return sa < sb ? -1 : 1;
+  const ts = String(va), tv = String(vb);
+  return ts < tv ? -1 : ts > tv ? 1 : 0;
+}
+
+// ── Gremlin Comparability semantics ──────────────────────────────────────────
+// Used by P.lt/lte/gt/gte: returns null (ERROR) for cross-type or NaN comparisons.
+
+function compareForPredicate(va: any, vb: any): number | null {
+  const ta = gremlinTypeOrder(va);
+  const tb = gremlinTypeOrder(vb);
+  if (ta !== tb) return null; // cross-type: ERROR
+  if (va === null || va === undefined) return 0; // null == null
+  if (typeof va === 'number') {
+    if (Number.isNaN(va) || Number.isNaN(vb)) return null; // NaN: ERROR
+    return compareNumeric(va as number, vb as number);
+  }
+  return compareValues(va, vb);
+}
+
+function getByValue(el: any, key: string | null | undefined): any {
+  if (key == null) return el;
+  if (key === 'T.id' || key === 'id') return el?.id;
+  if (key === 'T.label' || key === 'label') return el?.label;
+  if (el instanceof Vertex || el instanceof Edge) {
+    const vals = getValues(el, key);
+    return vals[0]; // first value for multi-valued properties
+  }
+  return el;
+}
+
+export function* stepOrder(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const key = args[0] as string | null | undefined;
+  const dir = ((args[1] as string) ?? 'asc').split('.').pop()!.toLowerCase();
+  const multiplier = dir === 'desc' ? -1 : 1;
+  const buf: StreamItem[] = [...source];
+  buf.sort((a, b) => {
+    const va = getByValue(getValue(a, trackPaths), key as string | null | undefined);
+    const vb = getByValue(getValue(b, trackPaths), key as string | null | undefined);
+    return multiplier * compareValues(va, vb);
+  });
+  yield* buf;
+}
+
+// ── Mutation steps ────────────────────────────────────────────────────────────
+
+export function* stepAddV(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const label = (args[0] as string | undefined) ?? 'vertex';
+  for (const _item of source) {
+    const id = graph.vertices.size;
+    const v = new Vertex(id, label, []);
+    graph.vertices.set(id, v);
+    yield wrap(v, [], trackPaths);
+  }
+}
+
+export function* stepAddEWithModulators(
+  source: Iterable<StreamItem>,
+  label: string,
+  fromPipeline: Arg[] | null,
+  toPipeline: Arg[] | null,
+  graph: Graph,
+  trackPaths: boolean,
+  executeSubPipeline: (pipeline: Arg[], graph: Graph) => any,
+): Generator<StreamItem> {
+  const fromVertex = fromPipeline
+    ? executeSubPipeline(fromPipeline, graph)
+    : null;
+  const toVertex = toPipeline
+    ? executeSubPipeline(toPipeline, graph)
+    : null;
+
+  if (!(fromVertex instanceof Vertex) || !(toVertex instanceof Vertex)) {
+    throw new Error("The addE step from/to must resolve to a Vertex or the ID of a Vertex present in the graph");
+  }
+
+  const id = graph.edges.size;
+  const edge = new Edge(id, fromVertex, label, toVertex, []);
+  graph.edges.set(id, edge);
+  graph._indexEdge(edge);
+  yield wrap(edge, [], trackPaths);
+}
+
+export function* stepProperty(
+  source: Iterable<StreamItem>, args: Arg[], graph: Graph, trackPaths: boolean,
+): Generator<StreamItem> {
+  const key = args[0] as string;
+  const value = args[1];
+  for (const item of source) {
+    const el = getValue(item, trackPaths);
+    if (el instanceof Vertex) {
+      // T.label sets the element label directly; T.id sets the id
+      if (key === 'T.label' || key === 'label') {
+        (el as any).label = value;
+      } else if (key === 'T.id' || key === 'id') {
+        const oldId = el.id;
+        (el as any).id = value;
+        graph.vertices.delete(oldId);
+        graph.vertices.set(value, el);
+      } else {
+        const existing = el.properties as VertexProperty[];
+        const idx = existing.findIndex(p => p.label === key);
+        const vpId = idx >= 0 ? existing[idx].id : existing.length;
+        const vp = new VertexProperty(vpId, key, value, []);
+        if (idx >= 0) existing[idx] = vp;
+        else existing.push(vp);
+      }
+    } else if (el instanceof Edge) {
+      const existing = el.properties as Property[];
+      const idx = existing.findIndex(p => p.key === key);
+      const newProp = new Property(key, value);
+      if (idx >= 0) (existing as any)[idx] = newProp;
+      else existing.push(newProp);
+    }
+    yield item;
+  }
+}
diff --git a/gremlin-js/gremlin-javascript/lib/process/local/types.ts b/gremlin-js/gremlin-javascript/lib/process/local/types.ts
new file mode 100644
index 0000000000..a081f017d7
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/lib/process/local/types.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { P, TextP, EnumValue } from '../traversal.js';
+
+/**
+ * A single element flowing through a Tiny Gremlin local pipeline.
+ * Carries the current value and its path history. No bulk count or sack —
+ * those are Java concerns not needed for local in-memory execution.
+ */
+export interface Traverser<T = any> {
+  value: T;
+  /** Path entries accumulated so far. Empty when trackPaths is false. */
+  path: PathEntry[];
+}
+
+export interface PathEntry {
+  labels: string[];
+  object: any;
+}
+
+/**
+ * A typed argument to a step descriptor. Pipeline is used for nested
+ * anonymous traversal arguments (e.g. the inner traversal of where()).
+ */
+export type Arg = string | number | bigint | boolean | null | P | TextP | EnumValue | Pipeline;
+
+/**
+ * A single step in a Tiny Gremlin pipeline: its name and fully-typed arguments.
+ * Nested traversals appear as Pipeline values within args.
+ */
+export interface StepDescriptor {
+  name: string;
+  args: Arg[];
+}
+
+/**
+ * An ordered sequence of StepDescriptors representing a complete Tiny Gremlin
+ * traversal or anonymous sub-traversal.
+ */
+export type Pipeline = StepDescriptor[];
diff --git a/gremlin-js/gremlin-javascript/lib/process/traversal.ts b/gremlin-js/gremlin-javascript/lib/process/traversal.ts
index a8f3abfc19..cca05ea201 100644
--- a/gremlin-js/gremlin-javascript/lib/process/traversal.ts
+++ b/gremlin-js/gremlin-javascript/lib/process/traversal.ts
@@ -204,6 +204,34 @@ export class Traversal {
     return itemDone;
   }
 
+  /**
+   * Calls fn for each element in the traversal results.
+   * @returns {Promise.<void>}
+   */
+  async forEach(fn: (element: any) => void): Promise<void> {
+    await this._applyStrategies();
+    while (true) {
+      const it = await this._getNext();
+      if (it.done) break;
+      fn(it.value);
+    }
+  }
+
+  /**
+   * Reduces the traversal results to a single value using fn and seed.
+   * @returns {Promise.<T>}
+   */
+  async reduce<T>(fn: (acc: T, element: any) => T, seed: T): Promise<T> {
+    await this._applyStrategies();
+    let acc = seed;
+    while (true) {
+      const it = await this._getNext();
+      if (it.done) break;
+      acc = fn(acc, it.value);
+    }
+    return acc;
+  }
+
   _applyStrategies() {
     if (this._traversalStrategiesPromise) {
       // Apply strategies only once
diff --git a/gremlin-js/gremlin-javascript/lib/structure/graph.ts b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
index 75baa70014..c318bfee2f 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/graph.ts
+++ b/gremlin-js/gremlin-javascript/lib/structure/graph.ts
@@ -30,10 +30,26 @@
 export class Graph {
   readonly vertices: Map<any, Vertex>;
   readonly edges: Map<any, Edge>;
+  /** Outgoing edges indexed by source vertex id. Populated during deserialization and mutation. */
+  readonly outEdges: Map<any, Edge[]>;
+  /** Incoming edges indexed by target vertex id. Populated during deserialization and mutation. */
+  readonly inEdges: Map<any, Edge[]>;
 
   constructor() {
     this.vertices = new Map();
     this.edges = new Map();
+    this.outEdges = new Map();
+    this.inEdges = new Map();
+  }
+
+  /** @internal Registers an edge in the adjacency index. Called by GraphSerializer and addE(). */
+  _indexEdge(edge: Edge): void {
+    const outId = edge.outV.id;
+    const inId = edge.inV.id;
+    if (!this.outEdges.has(outId)) this.outEdges.set(outId, []);
+    if (!this.inEdges.has(inId)) this.inEdges.set(inId, []);
+    this.outEdges.get(outId)!.push(edge);
+    this.inEdges.get(inId)!.push(edge);
   }
 
   toString() {
diff --git a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
index abf8ac5e2e..884ef0139d 100644
--- a/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
+++ b/gremlin-js/gremlin-javascript/lib/structure/io/binary/internals/GraphSerializer.js
@@ -198,6 +198,7 @@ export default class GraphSerializer {
 
       const edge = new Edge(eId, outV, eLabel, inV, edgeProps || []);
       graph.edges.set(eId, edge);
+      graph._indexEdge(edge);
     }
 
     return graph;
diff --git a/gremlin-js/gremlin-javascript/package.json b/gremlin-js/gremlin-javascript/package.json
index 0d704e0cf2..6ec9684149 100644
--- a/gremlin-js/gremlin-javascript/package.json
+++ b/gremlin-js/gremlin-javascript/package.json
@@ -93,6 +93,7 @@
     "TODO": "# test other mime types like graphbinary stringd",
     "features": "npm run features-graphbinary",
     "features-graphbinary": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' CLIENT_MIMETYPE='application/vnd.graphbinary-v4.0' cucumber-js --tags \"not @DataBigDecimal and not @DataBigInt and not @DataUUID and not @DataLong and not @StepWrite\" --import test/cucumber ../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/",
+    "test:tinygremlin": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' cucumber-js --tags @TinyGremlin --import test/cucumber/feature-steps.js --import test/cucumber/element-comparison.js --import test/cucumber/gremlin.js --import test/cucumber/local-world.ts ../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/",
     "features-docker": "npm run features-graphbinary-docker",
     "features-graphbinary-docker": "cross-env NODE_OPTIONS='--loader ts-node/esm' TS_NODE_PROJECT='tsconfig.test.json' CLIENT_MIMETYPE='application/vnd.graphbinary-v4.0' cucumber-js --tags \"not @DataBigDecimal and not @DataBigInt and not @DataUUID and not @DataLong and not @StepWrite\" --import test/cucumber ../gremlin-test/",
     "lint": "eslint --ext .js .",
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
index bd0aa8a83a..73ef8f48f3 100644
--- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js
@@ -564,6 +564,7 @@ const gremlins = {
     g_E_hasXlabel_nullX: [function({g}) { return g.E().has(T.label, null) }], 
     g_V_properties_hasLabelXnullX: [function({g}) { return g.V().properties().hasLabel(null) }], 
     g_V_hasNotXageX_name: [function({g}) { return g.V().hasNot("age").values("name") }], 
+    g_V_hasNotXlangX_name: [function({g}) { return g.V().hasNot("lang").values("name") }], 
     g_V_properties_hasValueXnullX: [function({g}) { return g.V().properties().hasValue(null) }], 
     g_V_properties_hasValueXnull_nullX: [function({g}) { return g.V().properties().hasValue(null, null) }], 
     g_V_properties_hasValueXnull_joshX_value: [function({g}) { return g.V().properties().hasValue(null, "josh").value() }], 
@@ -1033,7 +1034,7 @@ const gremlins = {
     g_addV_propertyXset_emptyX: [function({g}) { return g.addV("foo").property(Cardinality.set, new Map([])) }, function({g}) { return g.V().hasLabel("person").values() }], 
     g_addVXpersonX_propertyXname_joshX_propertyXage_nullX: [function({g}) { return g.addV("person").property("name", "josh").property("age", null) }, function({g}) { return g.V().has("person", "age", null) }], 
     g_addVXpersonX_propertyXname_markoX_propertyXfriendWeight_null_acl_nullX: [function({g}) { return g.addV("person").property("name", "marko").property("friendWeight", null, "acl", null) }, function({g}) { return g.V().has("person", "name", "marko").has("friendWeight", null) }, function({g}) { return g.V().has("person", "name", "marko").properties("friendWeight").has("acl", null) }, function({g}) { return g.V().has("person", "name", "marko").properties("friendWeight").count() }], 
-    g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: [function({g}) { return g.addV("person").property("name", "alice").property(Cardinality.single, "age", 50) }, function({g}) { return g.V().has("person", "name", "alice").property("age", __.union(__.values("age"), __.constant(1)).sum()) }, function({g}) { return g.V().has("person", "age", 50) }, function({g}) { return g.V().has("person", "age", 51) }], 
+    g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX: [function({g}) { return g.addV("person").property("name", "alice").property("age", 50) }, function({g}) { return g.V().has("person", "name", "alice").property("age", 51) }, function({g}) { return g.V().has("person", "age", 50) }, function({g}) { return g.V().has("person", "age", 51) }],
     g_V_limitX3X_addVXsoftwareX_aggregateXa1X_byXlabelX_aggregateXa2X_byXlabelX_capXa1_a2X_selectXa_bX_byXunfoldX_foldX: [function({g}) { return g.addV("person").property("name", "marko").property("age", 29).as("marko").addV("person").property("name", "vadas").property("age", 27).as("vadas").addV("software").property("name", "lop").property("lang", "java").as("lop").addV("person").property("name", "josh").property("age", 32).as("josh").addV("software").property("name", "ripple").property [...]
     g_addV_propertyXname_markoX_withXkey_valueX_valuesXname_keyX: [function({g}) { return g.addV().property("name", "marko").with_("key", "value").values("name", "key") }], 
     g_addV_propertyXname_marko_since_2010X_withXkey_valueX_propertiesXnameX_valuesXsince_keyX: [function({g}) { return g.addV().property("name", "marko", "since", 2010).with_("key", "value").properties("name").values("since", "key") }], 
@@ -1646,6 +1647,9 @@ const gremlins = {
     g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold: [function({g, vid1}) { return g.V(vid1).elementMap().order(Scope.local).by(Column.keys, Order.desc).unfold() }], 
     g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold: [function({g, vid1}) { return g.V(vid1).elementMap().order(Scope.local).by(Column.keys, Order.asc).unfold() }], 
     g_VX1X_valuesXageX_orderXlocalX: [function({g, vid1}) { return g.V(vid1).values("age").order(Scope.local) }], 
+    g_V_order_byXTidX_name: [function({g}) { return g.V().order().by(T.id).values("name") }], 
+    g_V_order_byXTid_descX_name: [function({g}) { return g.V().order().by(T.id, Order.desc).values("name") }], 
+    g_V_order_byXTlabelX_label: [function({g}) { return g.V().order().by(T.label).label() }], 
     g_V_pageRank_hasXpageRankX: [function({g}) { return g.V().pageRank().has("gremlin.pageRankVertexProgram.pageRank") }], 
     g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX: [function({g}) { return g.V().out("created").pageRank().with_("~tinkerpop.pageRank.edges", __.bothE()).with_("~tinkerpop.pageRank.propertyName", "projectRank").with_("~tinkerpop.pageRank.times", 0).valueMap("name", "projectRank") }], 
     g_V_pageRank_order_byXpageRank_descX_byXnameX_name: [function({g}) { return g.V().pageRank().order().by("gremlin.pageRankVertexProgram.pageRank", Order.desc).by("name").values("name") }], 
@@ -1666,6 +1670,7 @@ const gremlins = {
     g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX: [function({g, vid1}) { return g.withStrategies(new ProductiveByStrategy()).V(vid1).out().path().by("age") }], 
     g_injectX1_null_nullX_path: [function({g}) { return g.inject(1, null, null).path() }], 
     g_injectX1_null_nullX_path_dedup: [function({g}) { return g.inject(1, null, null).path().dedup() }], 
+    g_VX1X_out_out_path: [function({g, vid1}) { return g.V(vid1).out().out().path() }], 
     g_V_peerPressure_hasXclusterX: [function({g}) { return g.V().peerPressure().has("gremlin.peerPressureVertexProgram.cluster") }], 
     g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X: [function({g}) { return g.V().peerPressure().with_("~tinkerpop.peerPressure.propertyName", "cluster").with_("~tinkerpop.peerPressure.edges", __.outE("knows")).pageRank(1.0).with_("~tinkerpop.pageRank.propertyName", "rank").with_("~tinkerpop.pageRank.edges", __.outE("knows")).with_("~tinkerpop.pageRank.times", 1).group [...]
     g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX: [function({g}) { return g.V().has("name", "ripple").in_("created").peerPressure().with_("~tinkerpop.peerPressure.edges", __.outE()).with_("~tinkerpop.peerPressure.propertyName", "cluster").repeat(__.union(__.identity(), __.both())).times(2).dedup().valueMap("name", "cluster") }], 
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts b/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts
new file mode 100644
index 0000000000..ec22cc873e
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/cucumber/local-fixtures.ts
@@ -0,0 +1,231 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+/**
+ * Local graph fixtures for Tiny Gremlin Cucumber tests.
+ * Builds in-memory Graph instances that mirror the Java TinkerFactory graphs.
+ */
+
+import { Graph, Vertex, Edge, VertexProperty, Property } from '../../lib/structure/graph.js';
+
+function makeVertex(id: any, label: string, ...props: any[]) {
+  const v = new Vertex(id, label, []);
+  for (let i = 0; i < props.length; i += 3) {
+    // props: [vpId, key, value, ...]
+    v.properties.push(new VertexProperty(props[i], props[i + 1], props[i + 2], []));
+  }
+  return v;
+}
+
+function makeEdge(id: any, outV: Vertex, label: string, inV: Vertex, ...props: any[]) {
+  const e = new Edge(id, outV, label, inV, []);
+  for (let i = 0; i < props.length; i += 2) {
+    e.properties.push(new Property(props[i], props[i + 1]));
+  }
+  return e;
+}
+
+/**
+ * Builds the "modern" toy graph matching TinkerFactory.generateModern().
+ * Returns {graph, vertices, edges, vertexProperties} matching world.js cache format.
+ */
+export function buildModernGraph() {
+  const graph = new Graph();
+
+  const marko  = makeVertex(1,  'person',   0, 'name', 'marko',  1, 'age', 29);
+  const vadas  = makeVertex(2,  'person',   2, 'name', 'vadas',  3, 'age', 27);
+  const lop    = makeVertex(3,  'software', 4, 'name', 'lop',    5, 'lang', 'java');
+  const josh   = makeVertex(4,  'person',   6, 'name', 'josh',   7, 'age', 32);
+  const ripple = makeVertex(5,  'software', 8, 'name', 'ripple', 9, 'lang', 'java');
+  const peter  = makeVertex(6,  'person',  10, 'name', 'peter', 11, 'age', 35);
+
+  for (const v of [marko, vadas, lop, josh, ripple, peter]) {
+    graph.vertices.set(v.id, v);
+  }
+
+  const e7  = makeEdge(7,  marko, 'knows',   vadas,  'weight', 0.5);
+  const e8  = makeEdge(8,  marko, 'knows',   josh,   'weight', 1.0);
+  const e9  = makeEdge(9,  marko, 'created', lop,    'weight', 0.4);
+  const e10 = makeEdge(10, josh,  'created', ripple, 'weight', 1.0);
+  const e11 = makeEdge(11, josh,  'created', lop,    'weight', 0.4);
+  const e12 = makeEdge(12, peter, 'created', lop,    'weight', 0.2);
+
+  for (const e of [e7, e8, e9, e10, e11, e12]) {
+    graph.edges.set(e.id, e);
+    graph._indexEdge(e);
+  }
+
+  // vertices map: name → Vertex (with properties stripped, matching world.js format)
+  const vertices = new Map<string, any>();
+  for (const v of graph.vertices.values()) {
+    const nameVP = v.properties.find(p => (p as any).label === 'name');
+    if (nameVP) vertices.set((nameVP as any).value, { ...v, properties: undefined });
+  }
+
+  // edges map: "outName-label->inName" → Edge
+  const edges: Record<string, any> = {};
+  for (const e of graph.edges.values()) {
+    const outName = nameOf(e.outV);
+    const inName  = nameOf(e.inV);
+    const key = `${outName}-${e.label}->${inName}`;
+    edges[key] = { ...e, properties: undefined };
+  }
+
+  // vertexProperties map: "vertexName-key->formattedValue" → VertexProperty
+  const vertexProperties: Record<string, any> = {};
+  for (const v of graph.vertices.values()) {
+    const vName = nameOf(v);
+    if (!vName) continue;
+    for (const vp of v.properties as any[]) {
+      const k = vp.label;
+      let formattedVal = String(vp.value);
+      if (k === 'weight') formattedVal = 'd[' + vp.value + '].d';
+      else if (k === 'age' || k === 'since' || k === 'skill') formattedVal = 'd[' + vp.value + '].i';
+      vertexProperties[`${vName}-${k}->${formattedVal}`] = vp;
+    }
+  }
+
+  return { graph, vertices, edges, vertexProperties };
+}
+
+function nameOf(v: Vertex): string | null {
+  const vp = (v.properties as any[])?.find?.((p: any) => p.label === 'name');
+  return vp ? vp.value : null;
+}
+
+/**
+ * Builds the "sink" (kitchen sink) graph matching TinkerFactory.generateKitchenSink().
+ */
+export function buildSinkGraph() {
+  const graph = new Graph();
+
+  const loop = makeVertex(1000, 'loops', 1000, 'name', 'loop');
+  const a    = makeVertex(2000, 'message', 2000, 'name', 'a');
+  const b    = makeVertex(2001, 'message', 2001, 'name', 'b');
+
+  for (const v of [loop, a, b]) graph.vertices.set(v.id, v);
+
+  const eSelf     = makeEdge(1001, loop, 'self', loop);
+  const eLinkAB   = makeEdge(2002, a,    'link', b);
+  const eLinkAA   = makeEdge(2003, a,    'link', a);
+
+  for (const e of [eSelf, eLinkAB, eLinkAA]) {
+    graph.edges.set(e.id, e);
+    graph._indexEdge(e);
+  }
+
+  const vertices = new Map<string, any>();
+  for (const v of graph.vertices.values()) {
+    const nameVP = (v.properties as any[]).find((p: any) => p.label === 'name');
+    if (nameVP) vertices.set(nameVP.value, { ...v, properties: undefined });
+  }
+
+  // Build edge map for sink graph
+  const edges: Record<string, any> = {};
+  for (const e of graph.edges.values()) {
+    const outName = nameOf(e.outV);
+    const inName  = nameOf(e.inV);
+    const key = `${outName}-${e.label}->${inName}`;
+    edges[key] = { ...e, properties: undefined };
+  }
+
+  return { graph, vertices, edges, vertexProperties: {} as Record<string, any> };
+}
+
+/**
+ * Builds the "crew" graph matching TinkerFactory.generateTheCrew().
+ */
+export function buildCrewGraph() {
+  const graph = new Graph();
+
+  const marko      = makeVertex(1,  'person',   0,  'name', 'marko');
+  const stephen    = makeVertex(7,  'person',   1,  'name', 'stephen');
+  const matthias   = makeVertex(8,  'person',   2,  'name', 'matthias');
+  const daniel     = makeVertex(9,  'person',   3,  'name', 'daniel');
+  const gremlinV   = makeVertex(10, 'software', 4,  'name', 'gremlin');
+  const tinkergraph = makeVertex(11, 'software', 5, 'name', 'tinkergraph');
+
+  // location vertex properties (multi-valued — add as additional VPs)
+  addLocationVPs(marko,     6,  ['san diego', 1997, 2001], ['santa cruz', 2001, 2004], ['brussels', 2004, 2005], ['santa fe', 2005, null]);
+  addLocationVPs(stephen,   10, ['centreville', 1990, 2000], ['dulles', 2000, 2006], ['purcellville', 2006, null]);
+  addLocationVPs(matthias,  13, ['bremen', 2004, 2007], ['baltimore', 2007, 2011], ['oakland', 2011, 2014], ['seattle', 2014, null]);
+  addLocationVPs(daniel,    16, ['spremberg', 1982, 2005], ['kaiserslautern', 2005, 2009], ['aachen', 2009, null]);
+
+  for (const v of [marko, stephen, matthias, daniel, gremlinV, tinkergraph]) {
+    graph.vertices.set(v.id, v);
+  }
+
+  const edges_list = [
+    makeEdge(13, marko,    'develops',  gremlinV,    'since', 2009),
+    makeEdge(14, marko,    'develops',  tinkergraph, 'since', 2010),
+    makeEdge(15, marko,    'uses',      gremlinV,    'skill', 4),
+    makeEdge(16, marko,    'uses',      tinkergraph, 'skill', 5),
+    makeEdge(17, stephen,  'develops',  gremlinV,    'since', 2010),
+    makeEdge(18, stephen,  'develops',  tinkergraph, 'since', 2011),
+    makeEdge(19, stephen,  'uses',      gremlinV,    'skill', 5),
+    makeEdge(20, stephen,  'uses',      tinkergraph, 'skill', 4),
+    makeEdge(21, matthias, 'develops',  gremlinV,    'since', 2012),
+    makeEdge(22, matthias, 'uses',      gremlinV,    'skill', 3),
+    makeEdge(23, matthias, 'uses',      tinkergraph, 'skill', 3),
+    makeEdge(24, daniel,   'uses',      gremlinV,    'skill', 5),
+    makeEdge(25, daniel,   'uses',      tinkergraph, 'skill', 3),
+    makeEdge(26, gremlinV, 'traverses', tinkergraph),
+  ];
+
+  for (const e of edges_list) {
+    graph.edges.set(e.id, e);
+    graph._indexEdge(e);
+  }
+
+  const vertices = new Map<string, any>();
+  for (const v of graph.vertices.values()) {
+    const nameVP = (v.properties as any[]).find((p: any) => p.label === 'name');
+    if (nameVP) vertices.set(nameVP.value, { ...v, properties: undefined });
+  }
+
+  const edges: Record<string, any> = {};
+  for (const e of graph.edges.values()) {
+    const outName = nameOf(e.outV);
+    const inName  = nameOf(e.inV);
+    const key = `${outName}-${e.label}->${inName}`;
+    edges[key] = { ...e, properties: undefined };
+  }
+
+  return { graph, vertices, edges, vertexProperties: {} as Record<string, any> };
+}
+
+function addLocationVPs(v: Vertex, startId: number, ...locs: [string, number, number | null][]) {
+  for (let i = 0; i < locs.length; i++) {
+    const [city, start, end] = locs[i];
+    const vpId = startId + i;
+    const vp = new VertexProperty(vpId, 'location', city, []);
+    (vp as any).properties = [
+      new Property('startTime', start),
+      ...(end != null ? [new Property('endTime', end)] : []),
+    ];
+    (v.properties as VertexProperty[]).push(vp);
+  }
+}
+
+/**
+ * Returns an empty graph matching the "empty" graph used in world.js.
+ */
+export function buildEmptyGraph() {
+  return { graph: new Graph(), vertices: new Map<string, any>(), edges: {} as Record<string, any>, vertexProperties: {} as Record<string, any> };
+}
diff --git a/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts b/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts
new file mode 100644
index 0000000000..8ae8424d42
--- /dev/null
+++ b/gremlin-js/gremlin-javascript/test/cucumber/local-world.ts
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+/**
+ * Cucumber world for Tiny Gremlin (@TinyGremlin) local execution tests.
+ * Replaces the remote-connection world.js with local Graph instances.
+ * No Gremlin Server required.
+ */
+
+import { setWorldConstructor, Before } from '@cucumber/cucumber';
+import { buildModernGraph, buildSinkGraph, buildCrewGraph, buildEmptyGraph } from './local-fixtures.js';
+
+// Per-scenario graph fixtures, keyed by graph name.
+// Rebuilt fresh for every scenario to prevent cross-scenario mutation.
+const graphFixtures: Record<string, any> = {};
+
+function withConn(fixture: any): any {
+  fixture.connection = fixture.graph;
+  return fixture;
+}
+
+function resetFixtures() {
+  graphFixtures['modern'] = withConn(buildModernGraph());
+  graphFixtures['sink']   = withConn(buildSinkGraph());
+  graphFixtures['crew']   = withConn(buildCrewGraph());
+  graphFixtures['empty']  = withConn(buildEmptyGraph());
+}
+
+class TinkerPopLocalWorld {
+  scenario: string | null = null;
+  g: any = null;
+  traversal: any = null;
+  result: any = null;
+  cache: Record<string, any> | null = null;
+  graphName: string | null = null;
+  parameters: Record<string, any> = {};
+  sideEffects: Record<string, any> = {};
+  isGraphComputer = false;
+
+  getData() {
+    if (!this.graphName) throw new Error('Graph name is not set');
+    return graphFixtures[this.graphName];
+  }
+
+  cleanEmptyGraph() {
+    graphFixtures['empty'] = withConn(buildEmptyGraph());
+    return Promise.resolve();
+  }
+
+  loadEmptyGraphData() {
+    return Promise.resolve();
+  }
+}
+
+setWorldConstructor(TinkerPopLocalWorld);
+
+// Rebuild all graph fixtures before every scenario to prevent cross-scenario mutation.
+Before(function (this: TinkerPopLocalWorld, info: any) {
+  this.scenario = info.pickle.name;
+  resetFixtures();
+  this.cache = graphFixtures;
+});
+
+Before({ tags: '@GraphComputerOnly' },       () => 'skipped');
+Before({ tags: '@AllowNullPropertyValues' },  () => 'skipped');
+Before({ tags: '@StepWrite' },               () => 'skipped');
+Before({ tags: '@DataChar' },                () => 'skipped');
+Before({ tags: '@DataDuration' },            () => 'skipped');
diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py
index e35588e0bc..6ce019b0c0 100644
--- a/gremlin-python/src/main/python/tests/feature/gremlin.py
+++ b/gremlin-python/src/main/python/tests/feature/gremlin.py
@@ -538,6 +538,7 @@ world.gremlins = {
     'g_E_hasXlabel_nullX': [(lambda g:g.E().has(T.label, None))], 
     'g_V_properties_hasLabelXnullX': [(lambda g:g.V().properties().has_label(None))], 
     'g_V_hasNotXageX_name': [(lambda g:g.V().has_not('age').values('name'))], 
+    'g_V_hasNotXlangX_name': [(lambda g:g.V().has_not('lang').values('name'))], 
     'g_V_properties_hasValueXnullX': [(lambda g:g.V().properties().has_value(None))], 
     'g_V_properties_hasValueXnull_nullX': [(lambda g:g.V().properties().has_value(None, None))], 
     'g_V_properties_hasValueXnull_joshX_value': [(lambda g:g.V().properties().has_value(None, 'josh').value())], 
@@ -1620,6 +1621,9 @@ world.gremlins = {
     'g_VX1X_elementMap_orderXlocalX_byXkeys_descXunfold': [(lambda g, vid1=None:g.V(vid1).element_map().order(Scope.local).by(Column.keys, Order.desc).unfold())], 
     'g_VX1X_elementMap_orderXlocalX_byXkeys_ascXunfold': [(lambda g, vid1=None:g.V(vid1).element_map().order(Scope.local).by(Column.keys, Order.asc).unfold())], 
     'g_VX1X_valuesXageX_orderXlocalX': [(lambda g, vid1=None:g.V(vid1).values('age').order(Scope.local))], 
+    'g_V_order_byXTidX_name': [(lambda g:g.V().order().by(T.id_).values('name'))], 
+    'g_V_order_byXTid_descX_name': [(lambda g:g.V().order().by(T.id_, Order.desc).values('name'))], 
+    'g_V_order_byXTlabelX_label': [(lambda g:g.V().order().by(T.label).label())], 
     'g_V_pageRank_hasXpageRankX': [(lambda g:g.V().page_rank().has('gremlin.pageRankVertexProgram.pageRank'))], 
     'g_V_outXcreatedX_pageRank_withXedges_bothEX_withXpropertyName_projectRankX_withXtimes_0X_valueMapXname_projectRankX': [(lambda g:g.V().out('created').page_rank().with_('~tinkerpop.pageRank.edges', __.both_e()).with_('~tinkerpop.pageRank.propertyName', 'projectRank').with_('~tinkerpop.pageRank.times', 0).value_map('name', 'projectRank'))], 
     'g_V_pageRank_order_byXpageRank_descX_byXnameX_name': [(lambda g:g.V().page_rank().order().by('gremlin.pageRankVertexProgram.pageRank', Order.desc).by('name').values('name'))], 
@@ -1640,6 +1644,7 @@ world.gremlins = {
     'g_withStrategiesXProductiveByStrategyX_VX1X_out_path_byXageX': [(lambda g, vid1=None:g.with_strategies(ProductiveByStrategy()).V(vid1).out().path().by('age'))], 
     'g_injectX1_null_nullX_path': [(lambda g:g.inject(1, None, None).path())], 
     'g_injectX1_null_nullX_path_dedup': [(lambda g:g.inject(1, None, None).path().dedup())], 
+    'g_VX1X_out_out_path': [(lambda g, vid1=None:g.V(vid1).out().out().path())], 
     'g_V_peerPressure_hasXclusterX': [(lambda g:g.V().peer_pressure().has('gremlin.peerPressureVertexProgram.cluster'))], 
     'g_V_peerPressure_withXpropertyName_clusterX_withXedges_outEXknowsXX_pageRankX1X_byXrankX_withXedges_outEXknowsX_withXtimes_2X_group_byXclusterX_byXrank_sumX_limitX100X': [(lambda g:g.V().peer_pressure().with_('~tinkerpop.peerPressure.propertyName', 'cluster').with_('~tinkerpop.peerPressure.edges', __.out_e('knows')).page_rank(1.0).with_('~tinkerpop.pageRank.propertyName', 'rank').with_('~tinkerpop.pageRank.edges', __.out_e('knows')).with_('~tinkerpop.pageRank.times', 1).group().by(' [...]
     'g_V_hasXname_rippleX_inXcreatedX_peerPressure_withXedges_outEX_withyXpropertyName_clusterX_repeatXunionXidentity__bothX_timesX2X_dedup_valueMapXname_clusterX': [(lambda g:g.V().has('name', 'ripple').in_('created').peer_pressure().with_('~tinkerpop.peerPressure.edges', __.out_e()).with_('~tinkerpop.peerPressure.propertyName', 'cluster').repeat(__.union(__.identity(), __.both())).times(2).dedup().value_map('name', 'cluster'))], 
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
index ff0ad07295..d7e90db6b3 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json
@@ -9789,6 +9789,23 @@
             }
         ]
     },
+    {
+        "scenario": "g_V_hasNotXlangX_name",
+        "traversals": [
+            {
+                "original": "g.V().hasNot(\"lang\").values(\"name\")",
+                "language": "g.V().hasNot(\"lang\").values(\"name\")",
+                "canonical": "g.V().hasNot(\"lang\").values(\"name\")",
+                "anonymized": "g.V().hasNot(string0).values(string1)",
+                "dotnet": "g.V().HasNot(\"lang\").Values<object>(\"name\")",
+                "go": "g.V().HasNot(\"lang\").Values(\"name\")",
+                "groovy": "g.V().hasNot(\"lang\").values(\"name\")",
+                "java": "g.V().hasNot(\"lang\").values(\"name\")",
+                "javascript": "g.V().hasNot(\"lang\").values(\"name\")",
+                "python": "g.V().has_not('lang').values('name')"
+            }
+        ]
+    },
     {
         "scenario": "g_V_properties_hasValueXnullX",
         "traversals": [
@@ -34867,6 +34884,57 @@
             }
         ]
     },
+    {
+        "scenario": "g_V_order_byXTidX_name",
+        "traversals": [
+            {
+                "original": "g.V().order().by(T.id).values(\"name\")",
+                "language": "g.V().order().by(T.id).values(\"name\")",
+                "canonical": "g.V().order().by(T.id).values(\"name\")",
+                "anonymized": "g.V().order().by(T.id).values(string0)",
+                "dotnet": "g.V().Order().By(T.Id).Values<object>(\"name\")",
+                "go": "g.V().Order().By(gremlingo.T.Id).Values(\"name\")",
+                "groovy": "g.V().order().by(T.id).values(\"name\")",
+                "java": "g.V().order().by(T.id).values(\"name\")",
+                "javascript": "g.V().order().by(T.id).values(\"name\")",
+                "python": "g.V().order().by(T.id_).values('name')"
+            }
+        ]
+    },
+    {
+        "scenario": "g_V_order_byXTid_descX_name",
+        "traversals": [
+            {
+                "original": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "language": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "canonical": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "anonymized": "g.V().order().by(T.id, Order.desc).values(string0)",
+                "dotnet": "g.V().Order().By(T.Id, Order.Desc).Values<object>(\"name\")",
+                "go": "g.V().Order().By(gremlingo.T.Id, gremlingo.Order.Desc).Values(\"name\")",
+                "groovy": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "java": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "javascript": "g.V().order().by(T.id, Order.desc).values(\"name\")",
+                "python": "g.V().order().by(T.id_, Order.desc).values('name')"
+            }
+        ]
+    },
+    {
+        "scenario": "g_V_order_byXTlabelX_label",
+        "traversals": [
+            {
+                "original": "g.V().order().by(T.label).label()",
+                "language": "g.V().order().by(T.label).label()",
+                "canonical": "g.V().order().by(T.label).label()",
+                "anonymized": "g.V().order().by(T.label).label()",
+                "dotnet": "g.V().Order().By(T.Label).Label()",
+                "go": "g.V().Order().By(gremlingo.T.Label).Label()",
+                "groovy": "g.V().order().by(T.label).label()",
+                "java": "g.V().order().by(T.label).label()",
+                "javascript": "g.V().order().by(T.label).label()",
+                "python": "g.V().order().by(T.label).label()"
+            }
+        ]
+    },
     {
         "scenario": "g_V_pageRank_hasXpageRankX",
         "traversals": [
@@ -35207,6 +35275,23 @@
             }
         ]
     },
+    {
+        "scenario": "g_VX1X_out_out_path",
+        "traversals": [
+            {
+                "original": "g.V(vid1).out().out().path()",
+                "language": "g.V(vid1).out().out().path()",
+                "canonical": "g.V(vid1).out().out().path()",
+                "anonymized": "g.V(vid1).out().out().path()",
+                "dotnet": "g.V(vid1).Out().Out().Path()",
+                "go": "g.V(vid1).Out().Out().Path()",
+                "groovy": "g.V(vid1).out().out().path()",
+                "java": "g.V(vid1).out().out().path()",
+                "javascript": "g.V(vid1).out().out().path()",
+                "python": "g.V(vid1).out().out().path()"
+            }
+        ]
+    },
     {
         "scenario": "g_V_peerPressure_hasXclusterX",
         "traversals": [
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
index e559148f4b..2397577d0b 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Has.feature
@@ -18,6 +18,7 @@
 @StepClassFilter @StepHas
 Feature: Step - has()
 
+  @TinyGremlin
   Scenario: g_VX1X_hasXnameX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -30,6 +31,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_VX1X_hasXcircumferenceX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -40,6 +42,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_VX1X_hasXname_markoX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -52,6 +55,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_VX1X_hasXname_markovarX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -65,6 +69,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_VX2X_hasXname_markoX
     Given the modern graph
     And using the parameter vid1 defined as "v[vadas].id"
@@ -75,6 +80,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXname_markoX
     Given the modern graph
     And the traversal of
@@ -86,6 +92,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_blahX
     Given the modern graph
     And the traversal of
@@ -95,6 +102,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXage_gt_30X
     Given the modern graph
     And the traversal of
@@ -107,6 +115,7 @@ Feature: Step - has()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_VX1X_hasXage_gt_30X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -117,6 +126,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXpersonvar_age_gt_30X
     Given the modern graph
     And using the parameter xx1 defined as "person"
@@ -130,6 +140,7 @@ Feature: Step - has()
       | v[peter] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX4X_hasXage_gt_30X
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -142,6 +153,7 @@ Feature: Step - has()
       | result |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VXv1X_hasXage_gt_30X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -152,6 +164,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_VXv4X_hasXage_gt_30X
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -164,6 +177,7 @@ Feature: Step - has()
       | result |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out_hasXid_2X
     Given the modern graph
     And using the parameter vid2 defined as "v[josh].id"
@@ -176,6 +190,7 @@ Feature: Step - has()
       | result |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_V_hasXblahX
     Given the modern graph
     And the traversal of
@@ -185,6 +200,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXperson_name_markoX_age
     Given the modern graph
     And the traversal of
@@ -196,6 +212,7 @@ Feature: Step - has()
       | result |
       | d[29].i |
 
+  @TinyGremlin
   Scenario: g_V_hasXperson_name_markovarX_age
     Given the modern graph
     And using the parameter xx1 defined as "marko"
@@ -208,6 +225,7 @@ Feature: Step - has()
       | result |
       | d[29].i |
 
+  @TinyGremlin
   Scenario: g_V_hasXpersonvar_name_markoX_age
     Given the modern graph
     And using the parameter xx1 defined as "person"
@@ -220,6 +238,7 @@ Feature: Step - has()
       | result |
       | d[29].i |
 
+  @TinyGremlin
   Scenario: g_VX1X_outE_hasXweight_inside_0_06X_inV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -233,6 +252,7 @@ Feature: Step - has()
       | v[vadas] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_EX11X_outV_outE_hasXid_10X
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -259,7 +279,7 @@ Feature: Step - has()
       | result |
       | e[josh-created->ripple] |
 
-  @MultiProperties @MetaProperties
+  @MultiProperties @MetaProperties @TinyGremlin
   Scenario: g_V_hasXlocationX
     Given the crew graph
     And the traversal of
@@ -329,6 +349,7 @@ Feature: Step - has()
       | result |
       | d[2].l |
 
+  @TinyGremlin
   Scenario: g_V_hasXperson_age_withinX
     Given the modern graph
     And the traversal of
@@ -338,6 +359,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXperson_age_withoutX
     Given the modern graph
     And the traversal of
@@ -352,6 +374,7 @@ Feature: Step - has()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_containingXarkXX
     Given the modern graph
     And the traversal of
@@ -363,6 +386,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_startingWithXmarXX
     Given the modern graph
     And the traversal of
@@ -374,6 +398,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_endingWithXasXX
     Given the modern graph
     And the traversal of
@@ -407,6 +432,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_not_containingXarkXX
     Given the modern graph
     And the traversal of
@@ -422,6 +448,7 @@ Feature: Step - has()
       | v[ripple] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_not_startingWithXmarXX
     Given the modern graph
     And the traversal of
@@ -437,6 +464,7 @@ Feature: Step - has()
       | v[ripple] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_not_endingWithXasXX
     Given the modern graph
     And the traversal of
@@ -452,6 +480,7 @@ Feature: Step - has()
       | v[ripple] |
       | v[peter] |
 
+ @TinyGremlin
  Scenario: g_V_hasXname_regexXrMarXX
     Given the modern graph
     And the traversal of
@@ -463,6 +492,7 @@ Feature: Step - has()
       | result |
       | v[marko] |
 
+ @TinyGremlin
  Scenario: g_V_hasXname_notRegexXrMarXX
     Given the modern graph
     And the traversal of
@@ -478,6 +508,7 @@ Feature: Step - has()
       | v[ripple] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_regexXTinkerXX
     Given the empty graph
     And the graph initializer of
@@ -493,6 +524,7 @@ Feature: Step - has()
       | result |
       | Apache TinkerPop© |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_regexXTinkerUnicodeXX
     Given the empty graph
     And the graph initializer of
@@ -508,6 +540,7 @@ Feature: Step - has()
       | result |
       | Apache TinkerPop© |
 
+  @TinyGremlin
   Scenario: g_V_hasXp_neqXvXX
     Given the modern graph
     And the traversal of
@@ -541,6 +574,7 @@ Feature: Step - has()
       | v[marko] |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasXk_withinXcXX_valuesXkX
     Given the empty graph
     And the graph initializer of
@@ -558,6 +592,7 @@ Feature: Step - has()
       | ✦ |
       | ♠ |
 
+  @TinyGremlin
   Scenario: g_V_hasXnullX
     Given the modern graph
     And the traversal of
@@ -567,6 +602,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXnull_testnullkeyX
     Given the modern graph
     And the traversal of
@@ -576,6 +612,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_E_hasXnullX
     Given the modern graph
     And the traversal of
@@ -585,6 +622,7 @@ Feature: Step - has()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXlabel_personX
     Given the modern graph
     And the traversal of
@@ -599,6 +637,7 @@ Feature: Step - has()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXlabel_eqXpersonXX
     Given the modern graph
     And the traversal of
@@ -613,6 +652,7 @@ Feature: Step - has()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_V_hasXname_nullX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
index 818d55ec33..f942f920f7 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasId.feature
@@ -63,6 +63,7 @@ Feature: Step - hasId()
       | result |
       | d[6].l |
 
+  @TinyGremlin
   Scenario: g_V_hasIdXnullX
     Given the modern graph
     And the traversal of
@@ -72,6 +73,7 @@ Feature: Step - hasId()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasIdXeqXnullXX
     Given the modern graph
     And the traversal of
@@ -81,6 +83,7 @@ Feature: Step - hasId()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasIdX2_nullX
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -93,6 +96,7 @@ Feature: Step - hasId()
       | result |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasIdXmarkovar_vadasvarX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -148,6 +152,7 @@ Feature: Step - hasId()
       | v[marko] |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasIdXnull_2X
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -160,6 +165,7 @@ Feature: Step - hasId()
       | result |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasIdX1X_hasIdX2X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -171,6 +177,7 @@ Feature: Step - hasId()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_in_hasIdXneqX1XX
     Given the modern graph
     And using the parameter xx1 defined as "v[marko].id"
@@ -185,6 +192,7 @@ Feature: Step - hasId()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out_hasIdX2X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -198,6 +206,7 @@ Feature: Step - hasId()
       | result |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out_hasXid_2_3X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -241,6 +250,7 @@ Feature: Step - hasId()
       | result |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out_hasXid_2_3X_inList
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -255,6 +265,7 @@ Feature: Step - hasId()
       | v[vadas] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_V_hasXid_1_2X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -269,6 +280,7 @@ Feature: Step - hasId()
       | v[marko] |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasXid_1_2X_inList
     Given the modern graph
     And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id]"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
index 54e29938ad..d7eb23c968 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasLabel.feature
@@ -18,6 +18,7 @@
 @StepClassFilter @StepHasLabel
 Feature: Step - hasLabel()
 
+  @TinyGremlin
   Scenario: g_EX7X_hasLabelXknowsX
     Given the modern graph
     And using the parameter eid7 defined as "e[marko-knows->vadas].id"
@@ -30,6 +31,7 @@ Feature: Step - hasLabel()
       | result |
       | e[marko-knows->vadas] |
 
+  @TinyGremlin
   Scenario: g_E_hasLabelXknowsX
     Given the modern graph
     And the traversal of
@@ -42,7 +44,7 @@ Feature: Step - hasLabel()
       | e[marko-knows->vadas] |
       | e[marko-knows->josh] |
 
-  @MultiProperties @MetaProperties
+  @MultiProperties @MetaProperties @TinyGremlin
   Scenario: g_E_hasLabelXuses_traversesX
     Given the crew graph
     And the traversal of
@@ -62,6 +64,7 @@ Feature: Step - hasLabel()
       | e[matthias-uses->tinkergraph] |
       | e[gremlin-traverses->tinkergraph] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXperson_software_blahX
     Given the modern graph
     And the traversal of
@@ -78,6 +81,7 @@ Feature: Step - hasLabel()
       | v[lop] |
       | v[ripple] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXperson_softwarevarX
     Given the modern graph
     And using the parameter xx1 defined as "software"
@@ -95,6 +99,7 @@ Feature: Step - hasLabel()
       | v[lop] |
       | v[ripple] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_hasLabelXsoftwareX
     Given the modern graph
     And the traversal of
@@ -104,6 +109,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonvarX_hasLabelXsoftwareX
     Given the modern graph
     And using the parameter xx1 defined as "person"
@@ -114,6 +120,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonvar_softwarevarX
     Given the modern graph
     And using the parameter xx1 defined as "person"
@@ -144,6 +151,7 @@ Feature: Step - hasLabel()
       | vadas |
       | peter |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXnullX
     Given the modern graph
     And the traversal of
@@ -153,6 +161,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasXlabel_nullX
     Given the modern graph
     And the traversal of
@@ -162,6 +171,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXnull_nullX
     Given the modern graph
     And the traversal of
@@ -171,6 +181,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXnull_personX
     Given the modern graph
     And the traversal of
@@ -185,6 +196,7 @@ Feature: Step - hasLabel()
       | v[josh] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_E_hasLabelXnullX
     Given the modern graph
     And the traversal of
@@ -194,6 +206,7 @@ Feature: Step - hasLabel()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_E_hasXlabel_nullX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
index 10a24e50fe..404e6f0130 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/HasNot.feature
@@ -18,6 +18,7 @@
 @StepClassFilter @StepHasNot
 Feature: Step - hasNot()
 
+  @TinyGremlin
   Scenario: g_V_hasNotXageX_name
     Given the modern graph
     And the traversal of
@@ -28,4 +29,18 @@ Feature: Step - hasNot()
     Then the result should be unordered
       | result |
       | lop |
-      | ripple |
\ No newline at end of file
+      | ripple |
+  @TinyGremlin
+  Scenario: g_V_hasNotXlangX_name
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().hasNot("lang").values("name")
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | marko  |
+      | vadas  |
+      | josh   |
+      | peter  |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
index 3341bc3541..2bfb27dad0 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Range.feature
@@ -18,6 +18,7 @@
 @StepClassFilter @StepRange
 Feature: Step - range()
 
+  @TinyGremlin
   Scenario: g_VX1X_out_limitX2X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -33,6 +34,7 @@ Feature: Step - range()
       | v[lop] |
     And the result should have a count of 2
 
+  @TinyGremlin
   Scenario: g_VX1X_out_limitX2varX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -64,6 +66,7 @@ Feature: Step - range()
       | v[ripple] |
     And the result should have a count of 3
 
+  @TinyGremlin
   Scenario: g_VX1X_outXknowsX_outEXcreatedX_rangeX0_1X_inV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -78,6 +81,7 @@ Feature: Step - range()
       | v[ripple] |
     And the result should have a count of 1
 
+  @TinyGremlin
   Scenario: g_VX1X_outXknowsX_outXcreatedX_rangeX0_1X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -92,6 +96,7 @@ Feature: Step - range()
       | v[ripple] |
     And the result should have a count of 1
 
+  @TinyGremlin
   Scenario: g_VX1X_outXcreatedX_inXcreatedX_rangeX1_3X
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -107,6 +112,7 @@ Feature: Step - range()
       | v[peter] |
     And the result should have a count of 2
 
+  @TinyGremlin
   Scenario: g_VX1X_outXcreatedX_inXcreatedX_rangeX1var_3varX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -124,6 +130,7 @@ Feature: Step - range()
       | v[peter] |
     And the result should have a count of 2
 
+  @TinyGremlin
   Scenario: g_VX1X_outXcreatedX_inEXcreatedX_rangeX1_3X_outV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -231,6 +238,7 @@ Feature: Step - range()
       | m[{"b":"josh"}] |
       | m[{"b":"josh"}] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_order_byXageX_skipX1X_valuesXnameX
     Given the modern graph
     And the traversal of
@@ -244,6 +252,7 @@ Feature: Step - range()
       | josh |
       | peter |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_order_byXageX_skipX1varX_valuesXnameX
     Given the modern graph
     And using the parameter xx1 defined as "d[1].l"
@@ -293,6 +302,7 @@ Feature: Step - range()
       | result |
       | l[d[0.4].d,d[0.5].d,d[1.0].d,d[1.0].d] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_order_byXageX_valuesXnameX_skipX1X
     Given the modern graph
     And the traversal of
@@ -331,6 +341,7 @@ Feature: Step - range()
       | marko |
       | marko |
 
+  @TinyGremlin
   Scenario: g_V_rangeX2_1X
     Given the modern graph
     And the traversal of
@@ -341,6 +352,7 @@ Feature: Step - range()
     Then the traversal will raise an error with message containing text of "Not a legal range: [2, 1]"
 
   # iterated next variant of the previous test (which is iterated to list)
+  @TinyGremlin
   Scenario: g_V_rangeX3_2X
     Given the modern graph
     And the traversal of
@@ -436,6 +448,7 @@ Feature: Step - range()
       | result |
       | marko |
 
+  @TinyGremlin
   Scenario: g_VX5X_limitX1X_in_limitX1X_in_valuesXnameX
     Given the modern graph
     And using the parameter vid5 defined as "v[ripple].id"
@@ -497,6 +510,7 @@ Feature: Step - range()
       | result |
       | marko |
 
+  @TinyGremlin
   Scenario: g_VX5X_rangeX0_1X_in_rangeX0_1X_in_valuesXnameX
     Given the modern graph
     And using the parameter vid5 defined as "v[ripple].id"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
index 4ffc9420ca..b7b8be906c 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/Tail.feature
@@ -30,6 +30,7 @@ Feature: Step - tail()
       | ripple |
       | vadas |
 
+  @TinyGremlin
   Scenario: g_V_valuesXnameX_order_tailX2X
     Given the modern graph
     And the traversal of
@@ -42,6 +43,7 @@ Feature: Step - tail()
       | ripple |
       | vadas |
 
+  @TinyGremlin
   Scenario: g_V_valuesXnameX_order_tailX2varX
     Given the modern graph
     And using the parameter xx1 defined as "d[2].l"
@@ -55,6 +57,7 @@ Feature: Step - tail()
       | ripple |
       | vadas |
 
+  @TinyGremlin
   Scenario: g_V_valuesXnameX_order_tail
     Given the modern graph
     And the traversal of
@@ -66,6 +69,7 @@ Feature: Step - tail()
       | result |
       | vadas |
 
+  @TinyGremlin
   Scenario: g_V_valuesXnameX_order_tailX7X
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
index 02c2be3d7b..edb14a836f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddEdge.feature
@@ -450,7 +450,7 @@ Feature: Step - addE()
     And the graph should return 3 for count of "g.V(vid1).outE(\"knows\")"
     And the graph should return 1 for count of "g.V(vid1).out(\"knows\").has(\"name\",\"peter\")"
 
-  @AllowNullPropertyValues
+  @AllowNullPropertyValues @TinyGremlin
   Scenario: g_addEXknowsXpropertyXweight_nullXfromXV_hasXname_markoXX_toXV_hasXname_vadasXX
     Given the empty graph
     And the graph initializer of
@@ -466,7 +466,7 @@ Feature: Step - addE()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.E().has(\"knows\",\"weight\",null)"
 
-  @AllowNullPropertyValues
+  @AllowNullPropertyValues @TinyGremlin
   Scenario: g_addEXknowsvarXpropertyXweight_nullXfromXV_hasXname_markoXX_toXV_hasXname_vadasXX
     Given the empty graph
     And the graph initializer of
@@ -515,6 +515,7 @@ Feature: Step - addE()
       | result |
       | d[0.5].d |
 
+  @TinyGremlin
   Scenario: g_addEXknowsX_fromXV_hasXname_markoXX_toXV_hasXname_vadasXX_propertyXweight_0_5X_addEXknowsX_fromXV_hasXname_markoXX
     Given the empty graph
     And the graph initializer of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
index 99e1492f56..2dd651c493 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AddVertex.feature
@@ -473,6 +473,7 @@ Feature: Step - addV()
       | result |
       | name |
 
+  @TinyGremlin
   Scenario: g_addV_propertyXlabel_personX
     Given the empty graph
     And the traversal of
@@ -605,7 +606,6 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 0 for count of "g.V().hasLabel(\"foo\").values()"
 
-
   Scenario: g_addV_propertyXset_emptyX
     Given the empty graph
     And the traversal of
@@ -616,7 +616,7 @@ Feature: Step - addV()
     Then the result should have a count of 1
     And the graph should return 0 for count of "g.V().hasLabel(\"person\").values()"
 
-  @AllowNullPropertyValues
+  @AllowNullPropertyValues @TinyGremlin
   Scenario: g_addVXpersonX_propertyXname_joshX_propertyXage_nullX
     Given the empty graph
     And the traversal of
@@ -640,6 +640,35 @@ Feature: Step - addV()
     And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"friendWeight\").has(\"acl\",null)"
     And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"marko\").properties(\"friendWeight\").count()"
 
+  @TinyGremlin
+  Scenario: g_V_hasXperson_name_aliceX_propertyXage_51X
+    Given the empty graph
+    And the graph initializer of
+      """
+      g.addV("person").property("name", "alice").property("age", 50)
+      """
+    And the traversal of
+      """
+      g.V().has("person","name","alice").property("age", 51)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 0 for count of "g.V().has(\"person\",\"age\",50)"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"age\",51)"
+
+  @TinyGremlin
+  Scenario: g_addVXpersonX_propertyXname_aliceX_propertyXage_30X_query
+    Given the empty graph
+    And the traversal of
+      """
+      g.addV("person").property("name", "alice").property("age", 30)
+      """
+    When iterated to list
+    Then the result should have a count of 1
+    And the graph should return 1 for count of "g.V().has(\"person\",\"name\",\"alice\")"
+    And the graph should return 1 for count of "g.V().has(\"person\",\"age\",30)"
+
+
   Scenario: g_V_hasXperson_name_aliceX_propertyXsingle_age_unionXage_constantX1XX_sumX
     Given the empty graph
     And the graph initializer of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
index 670476742a..64ce226b2b 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Edge.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepE
 Feature: Step - E(), inV(), outV(), bothV(), otherV()
 
+  @TinyGremlin
   Scenario: g_E
     Given the modern graph
     And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | e[josh-created->lop] |
       | e[josh-created->ripple] |
 
+  @TinyGremlin
   Scenario: g_EX11X
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -58,6 +60,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | result |
       | e[josh-created->lop] |
 
+  @TinyGremlin
   Scenario: g_EXeid7_eid11X
     Given the modern graph
     And using the parameter eid7 defined as "e[marko-knows->vadas].id"
@@ -72,6 +75,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | e[marko-knows->vadas] |
       | e[josh-created->lop] |
 
+  @TinyGremlin
   Scenario: g_EXlistXeid7_eid11XX
     Given the modern graph
     And using the parameter xx1 defined as "l[e[marko-knows->vadas].id,e[josh-created->lop].id]"
@@ -85,6 +89,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | e[marko-knows->vadas] |
       | e[josh-created->lop] |
 
+  @TinyGremlin
   Scenario: g_EXnullX
     Given the modern graph
     And the traversal of
@@ -94,6 +99,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_EXlistXnullXX
     Given the modern graph
     And using the parameter xx1 defined as "l[null]"
@@ -104,6 +110,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_EX11_nullX
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -116,7 +123,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | result |
       | e[josh-created->lop] |
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_V_EX11X
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -134,7 +141,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | e[josh-created->lop] |
       | e[josh-created->lop] |
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_EX11X_E
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -152,7 +159,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | e[josh-created->lop] |
       | e[josh-created->ripple] |
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_V_EXnullX
     Given the modern graph
     And the traversal of
@@ -162,7 +169,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
     When iterated to list
     Then the result should be empty
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_V_EXlistXnullXX
     Given the modern graph
     And using the parameter xx1 defined as "l[null]"
@@ -202,6 +209,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
     Then the result should have a count of 1
     And the graph should return 1 for count of "g.E().hasLabel(\"tests\")"
 
+  @TinyGremlin
   Scenario: g_VX1X_outE_inV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -216,6 +224,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[josh] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX2X_inE_outV
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -228,6 +237,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_V_outE_hasXweight_1X_outV
     Given the modern graph
     And the traversal of
@@ -240,6 +250,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[marko] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outE_otherV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -254,6 +265,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[josh] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX4X_bothE_otherV
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -268,6 +280,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[ripple] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX4X_bothE_hasXweight_lt_1X_otherV
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -280,6 +293,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | result |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: get_g_VX1X_outE_otherV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -294,6 +308,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[josh] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outEXknowsX_inV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -307,6 +322,7 @@ Feature: Step - E(), inV(), outV(), bothV(), otherV()
       | v[vadas] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outEXknows_createdX_inV
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
index f119a2e964..999805cac0 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ElementMap.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepElementMap
 Feature: Step - elementMap()
 
+  @TinyGremlin
   Scenario: g_V_elementMap
     Given the modern graph
     And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - elementMap()
       | m[{"t[id]": "v[lop].id", "t[label]": "software", "name": "lop", "lang": "java"}] |
       | m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": "ripple", "lang": "java"}] |
 
+  @TinyGremlin
   Scenario: g_V_elementMapXname_ageX
     Given the modern graph
     And the traversal of
@@ -50,7 +52,7 @@ Feature: Step - elementMap()
       | m[{"t[id]": "v[lop].id", "t[label]": "software", "name": "lop"}] |
       | m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": "ripple"}] |
 
-  @GraphComputerVerificationReferenceOnly
+  @GraphComputerVerificationReferenceOnly @TinyGremlin
   Scenario: g_EX11X_elementMap
     Given the modern graph
     And using the parameter eid11 defined as "e[josh-created->lop].id"
@@ -63,6 +65,7 @@ Feature: Step - elementMap()
       | result |
       | m[{"t[id]": "e[josh-created->lop].id", "t[label]": "created", "weight": "d[0.4].d", "D[OUT]": "m[{\\"t[id]\\": \\"v[josh].id\\", \\"t[label]\\": \\"person\\"}]", "D[IN]": "m[{\\"t[id]\\": \\"v[lop].id\\", \\"t[label]\\": \\"software\\"}]"}] |
 
+  @TinyGremlin
   Scenario: g_V_elementMapXname_age_nullX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
index c49c6fca14..2780cecb6f 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Order.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepOrder
 Feature: Step - order()
 
+  @TinyGremlin
   Scenario: g_V_name_order
     Given the modern graph
     And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - order()
       | ripple |
       | vadas  |
 
+  @TinyGremlin
   Scenario: g_V_order_byXname_ascX_name
     Given the modern graph
     And the traversal of
@@ -50,6 +52,7 @@ Feature: Step - order()
       | ripple |
       | vadas  |
 
+  @TinyGremlin
   Scenario: g_V_order_byXnameX_name
     Given the modern graph
     And the traversal of
@@ -66,6 +69,7 @@ Feature: Step - order()
       | ripple |
       | vadas  |
 
+  @TinyGremlin
   Scenario: g_V_outE_order_byXweight_descX_weight
     Given the modern graph
     And the traversal of
@@ -96,6 +100,7 @@ Feature: Step - order()
       | m[{"a":"v[josh]","b":"v[ripple]"}] |
       | m[{"a":"v[josh]","b":"v[lop]"}] |
 
+  @TinyGremlin
   Scenario: g_V_both_hasLabelXpersonX_order_byXage_descX_limitX5X_name
     Given the modern graph
     And the traversal of
@@ -212,6 +217,7 @@ Feature: Step - order()
       | result |
       | l[v[vadas],v[marko],v[josh],v[peter]] |
 
+  @TinyGremlin
   Scenario: g_V_both_hasLabelXpersonX_order_byXage_descX_name
     Given the modern graph
     And the traversal of
@@ -246,6 +252,7 @@ Feature: Step - order()
       | v[ripple] |
       | v[vadas] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_order_byXageX
     Given the modern graph
     And the traversal of
@@ -548,4 +555,54 @@ Feature: Step - order()
     When iterated to list
     Then the result should be unordered
       | result |
-      | d[29].i |
\ No newline at end of file
+      | d[29].i |
+  @TinyGremlin
+  Scenario: g_V_order_byXTidX_name
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().order().by(T.id).values("name")
+      """
+    When iterated to list
+    Then the result should be ordered
+      | result |
+      | marko  |
+      | vadas  |
+      | lop    |
+      | josh   |
+      | ripple |
+      | peter  |
+
+  @TinyGremlin
+  Scenario: g_V_order_byXTid_descX_name
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().order().by(T.id, Order.desc).values("name")
+      """
+    When iterated to list
+    Then the result should be ordered
+      | result |
+      | peter  |
+      | ripple |
+      | josh   |
+      | lop    |
+      | vadas  |
+      | marko  |
+
+  @TinyGremlin
+  Scenario: g_V_order_byXTlabelX_label
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().order().by(T.label).label()
+      """
+    When iterated to list
+    Then the result should be ordered
+      | result   |
+      | person   |
+      | person   |
+      | person   |
+      | person   |
+      | software |
+      | software |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
index 36722662a1..adfdd7ccb1 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Path.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepPath
 Feature: Step - path()
 
+  @TinyGremlin
   Scenario: g_VX1X_name_path
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -82,6 +83,7 @@ Feature: Step - path()
       | result |
       | p[v[marko]] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outEXcreatedX_inV_inE_outV_path
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -161,4 +163,17 @@ Feature: Step - path()
     Then the result should be unordered
       | result |
       | p[d[1].i] |
-      | p[null] |
\ No newline at end of file
+      | p[null] |
+  @TinyGremlin
+  Scenario: g_VX1X_out_out_path
+    Given the modern graph
+    And using the parameter vid1 defined as "v[marko].id"
+    And the traversal of
+      """
+      g.V(vid1).out().out().path()
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result                          |
+      | p[v[marko],v[josh],v[ripple]] |
+      | p[v[marko],v[josh],v[lop]]    |
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
index b5b40fee17..1be665bda6 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Properties.feature
@@ -88,6 +88,7 @@ Feature: Step - properties()
       | lop |
       | ripple |
 
+  @TinyGremlin
   Scenario: g_V_valuesXname_age_nullX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
index 63f52324d3..62043514fa 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/ValueMap.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepValueMap
 Feature: Step - valueMap()
 
+  @TinyGremlin
   Scenario: g_V_valueMap
     Given the modern graph
     And the traversal of
@@ -34,6 +35,7 @@ Feature: Step - valueMap()
       | m[{"name": ["lop"], "lang": ["java"]}] |
       | m[{"name": ["ripple"], "lang": ["java"]}] |
 
+  @TinyGremlin
   Scenario: g_V_valueMapXtrueX
     Given the modern graph
     And the traversal of
@@ -66,6 +68,7 @@ Feature: Step - valueMap()
       | m[{"t[id]": "v[lop].id", "t[label]": "software", "name": ["lop"], "lang": ["java"]}] |
       | m[{"t[id]": "v[ripple].id", "t[label]": "software", "name": ["ripple"], "lang": ["java"]}] |
 
+  @TinyGremlin
   Scenario: g_V_valueMapXname_ageX
     Given the modern graph
     And the traversal of
@@ -82,6 +85,7 @@ Feature: Step - valueMap()
       | m[{"name": ["lop"]}] |
       | m[{"name": ["ripple"]}] |
 
+  @TinyGremlin
   Scenario: g_V_valueMapXtrue_name_ageX
     Given the modern graph
     And the traversal of
@@ -146,6 +150,7 @@ Feature: Step - valueMap()
       | m[{"t[id]": "v[lop].id", "name": "lop"}] |
       | m[{"t[id]": "v[ripple].id", "name": "ripple"}] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outXcreatedX_valueMap
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -195,6 +200,7 @@ Feature: Step - valueMap()
     When iterated next
     Then the traversal will raise an error with message containing text of "step can only have one by modulator"
 
+  @TinyGremlin
   Scenario: g_V_valueMapXname_age_nullX
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
index f9218ee462..91f6f65b06 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Vertex.feature
@@ -18,6 +18,7 @@
 @StepClassMap @StepVertex
 Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
 
+  @TinyGremlin
   Scenario: g_VXnullX
     Given the modern graph
     And the traversal of
@@ -27,6 +28,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_VXlistXnullXX
     Given the modern graph
     And using the parameter xx1 defined as "l[null]"
@@ -37,6 +39,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_VX1_nullX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -49,6 +52,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_VXlistX1_2_3XX_name
     Given the modern graph
     And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id,v[lop].id]"
@@ -63,6 +67,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | vadas |
       | lop |
 
+  @TinyGremlin
   Scenario: g_VXlistXv1_v2_v3XX_name
     Given the modern graph
     And using the parameter xx1 defined as "l[v[marko].id,v[vadas].id,v[lop].id]"
@@ -77,6 +82,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | vadas |
       | lop |
 
+  @TinyGremlin
   Scenario: g_V
     Given the modern graph
     And the traversal of
@@ -93,6 +99,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[ripple] |
       | v[peter] |
 
+  @TinyGremlin
   Scenario: g_VXv1X_out
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -107,6 +114,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[lop] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -121,6 +129,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[lop] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX2X_in
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -133,6 +142,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | result |
       | v[marko] |
 
+  @TinyGremlin
   Scenario: g_VX4X_both
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -147,6 +157,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[lop] |
       | v[ripple] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outE
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -161,6 +172,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | e[marko-knows->josh] |
       | e[marko-knows->vadas] |
 
+  @TinyGremlin
   Scenario: g_VX2X_outE
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -173,6 +185,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | result |
       | e[marko-knows->vadas] |
 
+  @TinyGremlin
   Scenario: g_VX4X_bothEXcreatedX
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -186,6 +199,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | e[josh-created->lop] |
       | e[josh-created->ripple] |
 
+  @TinyGremlin
   Scenario: g_VX4X_bothEXcreatedvarX
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -200,6 +214,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | e[josh-created->lop] |
       | e[josh-created->ripple] |
 
+  @TinyGremlin
   Scenario: g_VX4X_bothE
     Given the modern graph
     And using the parameter vid4 defined as "v[josh].id"
@@ -214,6 +229,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | e[josh-created->ripple] |
       | e[marko-knows->josh] |
 
+  @TinyGremlin
   Scenario: g_V_out_outE_inV_inE_inV_both_name
     Given the modern graph
     And the traversal of
@@ -234,6 +250,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | peter |
       | peter |
 
+  @TinyGremlin
   Scenario: g_VX2X_inE
     Given the modern graph
     And using the parameter vid2 defined as "v[vadas].id"
@@ -246,6 +263,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | result |
       | e[marko-knows->vadas] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outXknowsX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -272,6 +290,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[vadas] |
       | v[josh] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outXknows_createdX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -286,6 +305,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[josh] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX1X_outXknowsvar_createdvarX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -302,6 +322,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[josh] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_V_out_out
     Given the modern graph
     And the traversal of
@@ -314,6 +335,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | v[ripple] |
       | v[lop] |
 
+  @TinyGremlin
   Scenario: g_VX1X_out_out_out
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -324,6 +346,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
     When iterated to list
     Then the result should be empty
 
+  @TinyGremlin
   Scenario: g_VX1X_out_name
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -383,7 +406,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
     Then the result should be empty
     And the graph should return 3 for count of "g.V(vid1, vid2, vid3, vid4)"
 
-  @GraphComputerVerificationMidVNotSupported
+  @GraphComputerVerificationMidVNotSupported @TinyGremlin
   Scenario: g_V_hasLabelXpersonX_V_hasLabelXsoftwareX_name
     Given the modern graph
     And the traversal of
@@ -402,6 +425,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | ripple |
       | ripple |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXloopsX_bothEXselfX
     Given the sink graph
     And the traversal of
@@ -414,6 +438,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | e[loop-self->loop] |
       | e[loop-self->loop] |
 
+  @TinyGremlin
   Scenario: g_V_hasLabelXloopsX_bothXselfX
     Given the sink graph
     And the traversal of
@@ -449,7 +474,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | result |
       | v[marko] |
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_VX1X_V_valuesXnameX
     Given the modern graph
     And using the parameter vid1 defined as "v[marko].id"
@@ -467,7 +492,7 @@ Feature: Step - V(), out(), in(), both(), inE(), outE(), bothE()
       | ripple |
       | peter  |
 
-  @GraphComputerVerificationMidENotSupported
+  @GraphComputerVerificationMidENotSupported @TinyGremlin
   Scenario: g_V_outXknowsX_V_name
     Given the modern graph
     And the traversal of
diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
index 1e1a3c3397..e6601fb2ba 100644
--- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
+++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature
@@ -18,6 +18,7 @@
 @StepClassSemantics
 Feature: Orderability
 
+  @TinyGremlin
   Scenario: g_V_values_order
     Given the modern graph
     And the traversal of