Skip to content
Coding Sessions

Coding Sessions

core session is CORE’s own agentic coding agent. It clones a repository the org’s GitHub App can reach, drives the platform’s sovereign coder model turn by turn through a small tool set, and lands the result as a branch plus a draft pull request. The engine is grpc/internal/session; the CLI surface is grpc/internal/cli/session.go and sessionserve.go.

It is a background worker, not an interactive assistant. The defaults (--timeout 2h, --turn-timeout 15m, --max-turns 40) are sized for a CPU coder tier; the help text puts 40 turns at roughly an hour.

There is no REST call that starts a session, and no core session stop. Sessions start from the CLI (or from a workflow that runs the CLI). The console receives reports about them and can queue instructions for your own machine, as described below.

Commands

CommandPurpose
core session startStart a new session. Needs --repo owner/name and one of --prompt or --issue
core session resume <id>Resume a persisted session with a new instruction
core session listList persisted sessions
core session serveRun instructions queued for you in the console, on this machine
core session syncRe-send reports that could not be delivered

Flags for start and resume

FlagDefaultMeaning
--reporequiredowner/name of the repo to work in
--prompt / --issuenonethe goal; with both, the prompt is placed before the issue
--basethe default branchbranch to start from and target the draft PR at. An open PR’s head branch lands the fix on that PR
--branchcore-session/<id>branch name
--modelcoderinference model tier
--inference-urlauto-discoveredOpenAI-compatible endpoint (or CORE_SESSION_INFERENCE_URL)
--max-turns40hard cap on model turns before ending as partial
--timeout2h0m0swall-clock cap on the whole run
--turn-timeout15m0scap on a single generation; a runaway turn ends the run as partial
--shell-timeout2m0sper-command timeout for run_shell
--verifyoffself-verification tier: off, build or test (or CORE_SESSION_VERIFY)
--dry-runfalserun the full loop but never push or open a PR; print the diff
--interactivefalsedrop into a REPL after the instruction completes (/exit to stop)
--thinkfalselet the model emit its <think> trace (suppressed with /no_think by default)
--browserfalseenable the browser_* tools (needs Chrome/Chromium)
--browser-allow-remote, --browser-exec, --browser-timeout, --headedbrowser options; navigation is limited to localhost unless --browser-allow-remote
--no-browserfalseprint the GitHub sign-in code and URL instead of opening a browser

When --inference-url is empty the CLI probes, in order, http://127.0.0.1:35536, http://127.0.0.1:8081 and http://127.0.0.1:8080 for /v1/models, and logs which one answered.

The tool loop

Each turn the model returns one action, parsed as TOON with a JSON fallback (protocol.go). There is no native tool or function calling on this inference plane. After three malformed replies in a row the run fails with consecutive malformed replies, giving up.

ToolWhat it doesGated?
read_fileread a file in the workspaceno
list_dirlist a directoryno
write_filewrite a fileno
git_diffshow the working diffno
run_shellrun a shell commandyes: human y/N on every call
file_issuefile a GitHub issue for something it foundyes
browser_navigate, browser_click, browser_type, browser_read, browser_screenshot, browser_consoledrive a browser, only with --browseryes
donefinish the runlands only after done

Workspace confinement

tools.go refuses any path that escapes the workspace, is inside .git, or resolves through a symlink to outside the workspace or into .git. run_shell also carries a denylist as defence in depth (for example sudo, rm -rf /, curl … | sh, .git/config, a fork bomb). The approval gate is the primary control.

The approval gate

Every run_shell, browser_* and file_issue call prints the exact action and waits for approve? [y/N]. It fails closed: no terminal, EOF or anything but an explicit y declines, and the model sees declined: the human operator did not approve this command as an observation. In --interactive mode you are asked once more before landing: commit, push, and open a PR? [y/N].

Verify tiers

--verify lets the agent check its own work without a human y, for unattended runs such as the resolver and the forger.

  • off (default): every command goes to the approval gate.
  • build: an allowlist (go build/vet/list/mod, gofmt -l, flutter analyze) runs unattended. It compiles but does not run workspace code.
  • test: adds go test and flutter test, which do run workspace code, so they run only inside the bwrap sandbox.

CORE_SESSION_SANDBOX selects the sandbox: unset or auto uses bwrap when it can run; bwrap makes a missing sandbox fatal; off opts out, and then test refuses to start. A verification command may not use shell metacharacters, quoting or substitution. At the end, the run prints what it verified, or warns the agent ran no verification commands — this patch is UNVERIFIED.

Persistence and resume

Sessions live under ~/.core/sessions/<id>/ (directories 0700, files 0600):

  • transcript.jsonl is the full message history, replayed verbatim on resume, so it holds every prompt and completion.
  • session.json holds the metadata: id, repo, branch, issue, status, PR URL, turns, timestamps.
  • report.json holds the run report.

Statuses are running, done (the model called done), partial (hit max-turns or a timeout with uncommitted work preserved) and failed. AppendMessage also tightens the mode of transcripts written by older versions.

Reporting to the console

At the end of every session, including declined, dry-run and failed ones, the CLI POSTs a report to /api/session-runs on the configured console (--console-url, CORE_CONSOLE_URL or console_url). The bearer is the session’s own GitHub credential. The console accepts it only if the token carries the GitHub App’s rights for the console’s org (authorizeReporter).

If no console is configured, the report stays local and the CLI says so. If delivery fails, the report is kept pending and core session sync re-sends it. The console lists these runs on Agent runs › Runs.

Remote-control queue (core session serve)

The console can queue an instruction for your own machine. The laptop pulls; the console never dials a workstation.

EndpointWho calls itWhat it does
POST /api/session-commandsCLI, bearer tokenenqueue an instruction for yourself
GET /api/session-commands?wait=25score session servelong-poll: claim your next instruction
POST /api/session-commands/{id}/resultcore session servepost the outcome
GET /api/session-queuebrowser, session cookieview your queue
POST /api/session-queuebrowser, session cookieenqueue from the console

The rules, from sessioncommands.go:

  • Only a user-to-server token (core auth login) with a verified GitHub login is accepted. Installation tokens are refused.
  • The target is set by the server from the verified login. There is no field in which to name someone else’s machine.
  • Every enqueue, claim, result and refusal is audited with the verified actor.

core session serve itself is deliberately narrow:

  • --repo (repeatable) is required. There is no default and no wildcard.
  • The run_shell/browser_* gate is wired to a hard deny, because nobody is at that terminal.
  • It lands a commit and a draft PR only with --allow-land.
  • Defaults: --max-turns 12, --timeout 45m, --turn-timeout 12m, --poll-wait 25s. It keeps a hash-chained JSONL audit trail locally (--audit-file, under ~/.core/).
  • --once runs a single queued instruction and exits.

Where sessions run unattended

  • core-agent-resolver.yml runs core session start --base <PR head> with CORE_SESSION_VERIFY=build, raised to test only when a live bwrap probe passes (see Review Pipeline).
  • core-forge-run.yml (the forger) runs a session against the oldest open forge brief. It is disarmed until the Actions variable CORE_FORGE_ENABLED=true is set or the console control arms it.