Skip to content

Use Jev in review tools ​

WARNING

Experimental. @grok/agent-sdk/extensions/jev is experimental. Its exports and behavior can change in any release, without a deprecation period.

Use Jev when a review workflow needs a typed decision instead of prose. Jev returns a choice, a score, or a probability, and your TypeScript decides what happens next.

Set TYPESAFE_API_KEY, then mount the extension. The default model is jev-latest.

ts
// bot/extensions/jev.ts
import jev from "@grok/agent-sdk/extensions/jev";

export default jev();

On Cursor hosting, the mount adds api.typesafe.ai to egress and TYPESAFE_API_KEY to the expected secrets. Without the mount, add api.typesafe.ai to hosting.egressDomains yourself.

Start only the review turns you need ​

Call Jev from a channel hook before a model turn starts. This hook asks Jev whether a new pull request needs security review, based on its title, body, and file names. Below the threshold, the hook returns null, so the agent doesn't run or post anything.

ts
// bot/channels/github.ts
import {
  defaultGitHubAuth,
  githubChannel,
} from "@grok/agent-sdk/channels/github";
import { above, decide } from "@grok/agent-sdk/extensions/jev";

export default githubChannel({
  cursorAccount: { repos: ["acme/checkout"] },
  onPullRequest: async (ctx, pr) => {
    if (pr.action !== "opened" && pr.action !== "ready_for_review") {
      return null;
    }

    const octokit = await ctx.github.getOctokit();
    const files = await octokit.paginate(octokit.rest.pulls.listFiles, {
      owner: ctx.repository.owner,
      repo: ctx.repository.name,
      pull_number: pr.number,
    });
    const answers = await decide({
      state: {
        title: pr.title,
        body: pr.body,
        files: files.map(file => file.filename),
      },
      questions: {
        review: {
          type: "boolean",
          instructions: "Does this change touch auth, permissions, secrets, or external input?",
        },
      },
    });

    if (!above(answers.review, 0.8)) {
      return null;
    }
    return { auth: defaultGitHubAuth(ctx) };
  },
});

Filter findings before you post them ​

Let the model draft a finding, then ask Jev whether it's a real bug in the new code. The tool posts only the drafts that clear your threshold.

ts
// bot/tools/post_finding.ts
import { parseGitHubPrContinuationKey } from "@grok/agent-sdk/channels/github";
import { above, decide } from "@grok/agent-sdk/extensions/jev";
import { defineTool } from "@grok/agent-sdk/tools";
import { z } from "zod";

export default defineTool({
  description: "Post one security finding on this session's pull request.",
  inputSchema: z.object({
    summary: z.string(),
    draft: z.string().describe("The finding, in one or two sentences."),
  }),
  async execute({ summary, draft }, ctx) {
    if (ctx.session.purpose === "eval") {
      return { posted: false, reason: "eval" };
    }

    const ref = parseGitHubPrContinuationKey(ctx.session.continuationKey ?? "");
    if (ref === undefined) {
      throw new Error("post_finding requires a GitHub pull request session");
    }

    const answers = await decide({
      state: { summary, draft },
      questions: {
        real: {
          type: "boolean",
          instructions: "Is the draft an exploitable bug in the new code?",
        },
      },
    });

    if (!above(answers.real, 0.85)) {
      return { posted: false, reason: "clean" };
    }

    const octokit = await ctx.host.github.getOctokit();
    await octokit.rest.pulls.createReview({
      owner: ref.owner,
      repo: ref.repo,
      pull_number: ref.number,
      event: "COMMENT",
      body: draft,
    });
    return { posted: true };
  },
});

Approve changes by risk tier ​

Ask Jev for one risk tier. Approve only a confident very-low or low.

ts
import { decide, needsHuman } from "@grok/agent-sdk/extensions/jev";

const answers = await decide({
  state: { title, summary },
  questions: {
    risk: {
      type: "choice",
      instructions: "What risk tier is this pull request?",
      criteria: {
        "very-low": "docs, formatting, or a mechanical rename",
        low: "a local change with tests and no new trust boundary",
        medium: "auth, billing, or a behavior change callers depend on",
        high: "a likely exploit, data loss, or a broken public contract",
      },
    },
  },
});

const tier = answers.risk.choice;
if (needsHuman(answers.risk) || tier === "medium" || tier === "high") {
  return { verdict: "hold", tier };
}
return { verdict: "approve", tier };

Your tool resolves the pull request from the session and posts that verdict, as the Agentic Owners template does.

Write tools with Jev ​

Call decide to get the answer map, or evaluate to get { answers }. Use a boolean for a yes-or-no gate, a choice for a closed set such as risk tiers, and a score for an ordered rubric. above returns true when a probability or score meets your threshold. needsHuman returns true when the answer's confidence falls below 0.7 by default. Calibrate both on pull requests you've already labeled.

Ask Jev one question per call, and write the rule that combines the answers in your own code.

Let the agent ask Jev ​

The mount also gives the model a read-only tool named <namespace>__evaluate, which is jev__evaluate with the mount shown earlier. The model can call it during a turn, and it never posts or approves anything. When the answer and the write belong together, call decide inside a project tool instead.

Choose what to mount ​

Mounting Jev also adds two skills. jev__questions teaches the model to write atomic questions and read the answers, and jev__gated-write teaches it to put decide and the write in one server tool. Set harnessTools: false to keep Jev inside your own tools, or skills: false if your agent has its own Jev instructions.

ts
export default jev({ harnessTools: false, skills: true });

Neither switch removes the exported helpers, so project tools can still import decide, above, and needsHuman.