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.
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
| Command | Purpose |
|---|---|
core session start | Start 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 list | List persisted sessions |
core session serve | Run instructions queued for you in the console, on this machine |
core session sync | Re-send reports that could not be delivered |
Flags for start and resume
| Flag | Default | Meaning |
|---|---|---|
--repo | required | owner/name of the repo to work in |
--prompt / --issue | none | the goal; with both, the prompt is placed before the issue |
--base | the default branch | branch to start from and target the draft PR at. An open PR’s head branch lands the fix on that PR |
--branch | core-session/<id> | branch name |
--model | coder | inference model tier |
--inference-url | auto-discovered | OpenAI-compatible endpoint (or CORE_SESSION_INFERENCE_URL) |
--max-turns | 40 | hard cap on model turns before ending as partial |
--timeout | 2h0m0s | wall-clock cap on the whole run |
--turn-timeout | 15m0s | cap on a single generation; a runaway turn ends the run as partial |
--shell-timeout | 2m0s | per-command timeout for run_shell |
--verify | off | self-verification tier: off, build or test (or CORE_SESSION_VERIFY) |
--dry-run | false | run the full loop but never push or open a PR; print the diff |
--interactive | false | drop into a REPL after the instruction completes (/exit to stop) |
--think | false | let the model emit its <think> trace (suppressed with /no_think by default) |
--browser | false | enable the browser_* tools (needs Chrome/Chromium) |
--browser-allow-remote, --browser-exec, --browser-timeout, --headed | browser options; navigation is limited to localhost unless --browser-allow-remote | |
--no-browser | false | print 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.
| Tool | What it does | Gated? |
|---|---|---|
read_file | read a file in the workspace | no |
list_dir | list a directory | no |
write_file | write a file | no |
git_diff | show the working diff | no |
run_shell | run a shell command | yes: human y/N on every call |
file_issue | file a GitHub issue for something it found | yes |
browser_navigate, browser_click, browser_type, browser_read, browser_screenshot, browser_console | drive a browser, only with --browser | yes |
done | finish the run | lands 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: addsgo testandflutter 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.jsonlis the full message history, replayed verbatim on resume, so it holds every prompt and completion.session.jsonholds the metadata: id, repo, branch, issue, status, PR URL, turns, timestamps.report.jsonholds 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.
| Endpoint | Who calls it | What it does |
|---|---|---|
POST /api/session-commands | CLI, bearer token | enqueue an instruction for yourself |
GET /api/session-commands?wait=25s | core session serve | long-poll: claim your next instruction |
POST /api/session-commands/{id}/result | core session serve | post the outcome |
GET /api/session-queue | browser, session cookie | view your queue |
POST /api/session-queue | browser, session cookie | enqueue 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/). --onceruns a single queued instruction and exits.
Where sessions run unattended
core-agent-resolver.ymlrunscore session start --base <PR head>withCORE_SESSION_VERIFY=build, raised totestonly when a live bwrap probe passes (see Review Pipeline).core-forge-run.yml(theforger) runs a session against the oldest openforgebrief. It is disarmed until the Actions variableCORE_FORGE_ENABLED=trueis set or the console control arms it.