Repository navigation
feat(nanocodex): run nanocodex as a durable Rivet Actor - #333
eersnington wants to merge 3 commits into
Conversation
|
This PR was not deployed automatically as @eersnington does not have access to the Railway project. In order to get automatic PR deploys, please add @eersnington to your workspace on Railway. |
e6b7544 to
1e73e2f
Compare
|
@gakonst hey! I saw that you've cooked up an example of nanocodex on rivet through wasm, and i was stoked to see it!!! so I have something neat here for you too. this is nanocodex agent on rivet actors where i mapped the lifecycle of nanocodex 1:1 onto our actor lifecycle. it's not coupled to agentos, nor does it require a vm to start, and it runs lean on our actor process entirely quick rundown of what user facing code looks like /```ts
// actor.ts
import { nanocodex } from "@rivet-dev/nanocodex";
import { daytonaProvider } from "@rivet-dev/sandbox-adapter/daytona";
import { Transport } from "nanocodex/node";
import { setup } from "rivetkit";
export const coder = nanocodex({
transport: () => Transport.openAi({ apiKey: process.env.OPENAI_API_KEY! }), // or chatGptSubscription((c) => ...) with a chatGptCredentials() actor as a credentials actor (we have an example of this in pi
sandbox: daytonaProvider(), // or any from sandbox-adapter
actions: {
// your own actions get the agent as c.nanocodex
review: async (c, pr: number) => {
const turn = c.nanocodex.turn.prompt({ input: `review PR #${pr}` });
return (await turn.result()).finalMessage;
},
},
});
export const registry = setup({ use: { coder } });
registry.start();// client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./actor";
const client = createClient<typeof registry>("http://localhost:6420");
const coder = client.coder.getOrCreate(["issue-42"]);
coder.connect().on("event", (event) => console.log(event.type));
const id = crypto.randomUUID();
const input = "fix issue 42";
coder.turn.prompt({ id, input }); // starts the turn on the actor
// later, from any client, after a disconnect, a retry, or a deploy:
// the same id joins the running turn, returns the saved result, or resumes it
const { finalMessage } = await coder.turn.prompt({ id, input });i'm not entirely familiar with nanocodex's api, so if anything looks dubious here, i'm happy to walk through more and take suggesstions =) and, here's a list of things this
not entirely sure if we're gonna include this in our release pipeline as we do for pi and pi durable, but this is a fun experiment regardless. |
|
ooooooooooooooooo amazing thank you! ill share some more thoughts in a bit. any feedback so far? |
Adds
@rivet-dev/nanocodex: the nanocodex coding agent running inside a Rivet Actor, with its conversation in the actor's SQLite.nanocodex()wraps nanocodex'sAgent.create.transportreturns nanocodex's ownTransport,agenttakes its ownAgent.createoptions, the actions mirror itsturn.*andsession.*methods, and app actions get the agent asc.nanocodex.c.db. Prompting a finished turn id returns its result without a model call; prompting a running id joins that turn.sandboxtakes any@rivet-dev/sandbox-adapterprovider. The model gets nanocodex's own file tools through its workspace interface, plus a one-shotexec_command.chatGptSubscription()runs on a ChatGPT subscription held by achatGptCredentials()actor, picked per agent likepi({ credentials }). nanocodex signs in and refreshes; the credentials actor stores the credential state and sends each token refresh once, because a refresh token rotates.scripts/check-boundaries.mjsapplies the pi package rules to the new package.Security
chatGptCredentials()accepts only callers that pass itssecretconnection parameter. Conversation actors read the stored credential state, including the refresh token, through that check.