Grid Console is agent-agnostic. It owns the board, the stages, the card contracts and the code index; a provider plugin runs the sessions and supplies the words each stage starts with. The alpha bundles three providers: Claude, built on Claude Code; Codex, built on Codex CLI and marked unstable; and Copilot, built on GitHub Copilot CLI, also unstable and the first provider written against the spawn contract below.
Grid never runs a command line for a stage by hand. It starts the agent process the provider's manifest describes, talks to it over its own transport, and sends one message when a session starts or is picked back up in a stage. The provider contributes that message, per stage. Since plugin API 0.2 it may also describe how to start the process itself, so a third-party agent needs no line of code in core at all.
| POINT | WHAT IT CARRIES |
|---|---|
agent.provider |
The session: an id, a display name, a bin (decorative unless a permissions.spawn block also names it), and six stage prompts (prepare, start, build, review, deliver, verify). The session runtime reads this point when it spawns a session; for a provider whose manifest declares permissions.spawn, that block, rather than this payload, is what actually starts the program, tails its transcript and installs its hooks. See Adding a provider. |
llm.provider |
Declared by the bundled providers and read by nothing yet. The model pickers read the installed Claude CLI rather than any plugin. |
Each prompt resolves in a fixed order, first hit wins: your edit on that arrow in Settings › SDLC, then a file in the repository (.claude/commands/<stage>.md for Claude, .codex/prompts/<stage>.md for Codex), then the shipped default. Grid never writes those files. Restore default deletes your edit, so the file wins again if there is one. start is what a card whose type has no Prepare stage gets, when it starts and when it is picked back up in Doing.
Settings › Agents and models opens with the AGENT PROVIDER radio. The rows come from a probe of your PATH: INSTALLED or NOT FOUND, and UNKNOWN until the probe has answered. Installed is not selectable. A row can be picked only when Grid has a spawn path for it.
| PROVIDER | TODAY |
|---|---|
| Claude (Claude Code) | Bundled. Spawnable. The default. |
| Codex CLI | Bundled, marked unstable. Spawnable. Put codex on PATH or set codexBin in ~/.grid/config.json. |
| Copilot (GitHub Copilot CLI) | Bundled as a plugin, marked unstable. Spawnable once its first-run binary approval is confirmed on Settings › Agents and models. Put copilot on PATH, or pin its path from the row's Set path… control. |
| Anything else | Not bundled. A third party can write a plugin that declares permissions.spawn in its manifest (see Adding a provider), and its row appears here, real and selectable, the moment Grid loads it and its binary is approved. No ollama-provider or Gemini plugin ships today; nothing stops one shipping tomorrow. |
The rows are not a fixture: GET /api/agent-providers draws one row per bundled entry plus one per currently loaded plugin that contributes agent.provider with a usable spawn contract, so a workspace with no third-party provider installed sees exactly two rows and one with copilot-provider enabled sees three.
An interactive Copilot session opens with a Confirm folder trust modal naming its working directory, and waits for a keypress before it will run a single turn. That was verified against @github/copilot 1.0.82 by driving a real daemon. Neither --allow-all-tools (which Grid already sends) nor --allow-all-paths nor --add-dir <dir> skips it; all three were tried. Left alone, the modal is the exact stall folder trust already causes for Claude: no transcript, no hook, and the board reporting an agent that is working.
copilot-provider asks for that by name, with "trust": "copilot-config" in its spawn block, and Grid pre-seeds it the same way it does for Claude's own trust dialog (see worktrees). Copilot records the answer in $COPILOT_HOME/config.json under trustedFolders, and Grid points COPILOT_HOME at its own state directory ({stateDir}/copilot-home) rather than at the user's real ~/.copilot, so before spawning a copilot-provider session Grid writes the session's working directory into that file itself, gated by the same boundary Claude's own pre-seed uses: only a directory the user themselves put into Grid (the project's configured folder, a worktree Grid cut, or the workspace root) is asserted trusted. A directory that arrived some other way is left exactly as it was. The modal stands, and the session log says why.
The model a card runs on is a lookup rather than a setting you pick. The workspace budget (eco, balanced, quality, max) crossed with the card's difficulty (easy, normal, hard, vhard) and the role its stage plays (research and planning for Prepare and Review, implementation for Doing, delivery and validate for Deliver and Verify) resolves a model and an effort. Hand-edited cells survive a budget change. Haiku is never a default: the table seeds Sonnet, Opus and Fable only. The model names come from the installed claude binary, so a new family appears without an update. See models, effort and cost.
The result is passed to Claude as --model and --effort. Codex receives it through App Server when Grid starts or resumes the thread and when it sends a turn; the attached native TUI receives neither flag. Insights still records no model for Codex sessions rather than inferring one from the workspace matrix.
A plugin-contributed provider decides for itself, flag by flag, in its manifest's permissions.spawn.argv: a placeholder Grid has no value for is dropped, and so is the literal flag in front of it (see the dropping rule in Adding a provider). Grid sends Copilot neither a model nor an effort flag, and its argv names neither placeholder. Two separate incompatibilities put it there, both verified by running the CLI:
@github/copilot 1.0.82 refuses --model auto alongside --effort <level> ("Model "auto" does not support reasoning effort configuration"), and refuses --effort alone the same way, because Copilot's own default model is auto. So {effort} cannot be sent.{model} is filled from the workspace matrix (sessions.js agentConfigFor), so it always has a value and that value is always Grid's own vocabulary. A live session spawned --model sonnet and Copilot answered Error: Model "sonnet" from --model flag is not available. The spawn contract has no field for mapping one vocabulary to the other, so the flag is not sent and Copilot resolves its own default (auto, confirmed in the session transcript's session.model_change event).The consequence is worth stating plainly rather than leaving to be discovered: the compute-budget matrix does not reach a Copilot session. Every Copilot card runs on Copilot's default model whatever the budget tiles say. Insights records no model for those sessions rather than one that was never sent, the same rule Codex follows.
| ASPECT | DETAIL |
|---|---|
| Needs | Claude Code installed and signed in on your machine. Grid signs nothing in and stores no credential. |
| Prompt files | .claude/commands/prepare.md, start.md, build.md, self-review.md, deliver.md and verify.md, read fresh at every session start. |
| Settings rows | defaultModel (stored; the matrix decides what a session gets), mcpConfig pointing at .mcp.json, skillsDir pointing at .claude/skills. The last two are pointers: they name the files the CLI reads rather than carrying values of their own. |
| Driving a browser | Verify steps that need a live browser require the Claude in Chrome extension paired with Claude Code, once per machine. Grid detects the pairing from the native-messaging host manifest it writes; without it the step is recorded as skipped, neither a pass nor a failure. On Windows the pairing lives in the registry, so Grid reports it as unknown. |
| Read back | Transcript rows, tool calls, files touched and tokens. Cost is derived in core from the transcripts at published API rates, so the figure is a cost-equivalent rather than a bill. |
Grid also reads what the installed CLI brings to a session: MCP server health through claude mcp list (real connections, so on demand only), the Claude Code plugin roster through claude plugin list --json, and skills from ~/.claude/skills and the project's .claude/skills. These are read-only readouts over the engine's local API; no Settings page draws them in this build. Adding a server stays with claude mcp, because adding one means handling its keys, and Grid never holds those.
| ASPECT | DETAIL |
|---|---|
| Needs | codex on PATH, or codexBin set to an absolute path. |
| How it runs | A native Codex TUI with an App Server bridge beside it. Grid configures model, effort, the workspace-write sandbox, approval routing and the project/card roots through App Server thread/start or thread/resume, then attaches Terminal with codex resume <threadId> --remote <endpoint> --no-alt-screen. The TUI gets no task or permission overrides; Grid observes the session and sends turns over the WebSocket, so Terminal and Chat remain one conversation. |
| Prompt files | .codex/prompts/<stage>.md, the same six stages. |
| Settings rows | defaultModel (free text), sandbox (read-only, workspace-write or danger-full-access), mcpConfig pointing at .codex/config.toml. |
| Why unstable | Its stage prompts have had far less real use than Claude's. The manifest carries the note, and the Plugins pane shows it under the UNSTABLE chip. |
| Questions | Codex has no AskUserQuestion tool. A question is a message in the conversation. |
Insights prices Claude Code transcripts. Codex sessions are not priced yet.
Settings › Accounts registers Codex logins as well as Claude ones. Pick Codex on the ADD AN ACCOUNT row, name it, and Grid makes a ~/.codex-<name> directory of its own and copies just two keys (model and model_reasoning_effort) out of your ~/.codex/config.toml. Nothing else is copied: the rest of that file is your MCP servers, your notify hooks and your per-project trust decisions, and a new login has agreed to none of them.
The account has no credential yet. Grid never handles a provider login, so signing in is a command you run yourself, printed under the form and on the row until it succeeds:
CODEX_HOME="/Users/you/.codex-work" codex login
Press Check on the row afterwards. Grid runs codex login status against that home and the row reads SIGNED IN with the plan phrase Codex reports (using ChatGPT), or SIGNED OUT. If codex is not on your PATH at all the row says that instead of saying signed out. They are two different problems with two different fixes.
A card can only bill to an account of the provider it runs on, or to Primary, which means each CLI's own default home and works for either. Binding a Codex card to a Claude account would point CODEX_HOME at a directory holding no auth.json, so the session would start logged out; Grid refuses that spawn instead, puts the reason on the card, and the account picker in the session header lists only the accounts that card could actually use. Pools follow the same rule: one provider per pool, as one billing type per pool. A pool is a named set of accounts a project spawns across, made on Settings › Accounts: each session takes the next member in a round robin, a member whose usage window is spent is skipped and rejoins once the hold lapses, and a project picks its pool on its own Settings tab.
Copilot cannot have a second account, and the picker says so rather than leaving it out: its OAuth token lives in the OS keychain under one fixed service name, so there is no per-account directory to register. Its own /user switch moves a single global pointer rather than switching between two isolated logins.
Grid also pre-answers Codex's folder-trust prompt for the account it is about to bill, the same way it does for Claude and Copilot. It writes [projects."<dir>"] trust_level = "trusted" into that account's own config.toml, leaving your repository alone, and only for a directory you yourself put into Grid.
Every provider, bundled or not, declares agent.provider in points and contributes { id, name, bin, prompts } from activate(context); each prompt entry carries stage, name, title, file, usedBy and default. That is the whole contract for the six stage prompts, and it changes every word a stage starts with, whoever starts the process underneath it.
Starting the process is a second, separate declaration: permissions.spawn, in the manifest, behind apiVersion: ">=0.2" (the plugin API itself is 0.2.0). It lives in the manifest rather than in the contributed payload for three reasons that matter to whoever is deciding to trust it: it is signed, inside manifestHash, inside the Ed25519 payload a .gridplugin bundle carries, where a contributed payload is signed by nothing at all; it is shown, printed to the person installing the plugin and shown again, resolved to a real path on their machine, before the binary ever runs the first time; and it is comparable, so a plugin update that changes so much as one argv element is treated as a widening and refused until someone re-confirms it. A contributed agent.provider payload may only reference this block (spawn: true); a payload that tries to restate it is dropped rather than trusted.
copilot-provider is the worked example, and the manifest this section quotes: it fills eight of the nine spawn fields, more than anything else in the bundle. Its manifest is at ../gridconsole-plugins/plugins/copilot-provider/grid-plugin.json; read it alongside this section rather than in place of it. Its stability note records live runs against @github/copilot 1.0.83.
claude-provider declares a spawn block of its own (bin, argv, resumeArgv, forkArgv, env, sessionId and trust), so the contract below governs how Grid starts Claude Code too, and it is the one manifest that uses forkArgv, the argv a card forked from another card's conversation is started on. codex-provider declares none, and core starts a Codex session itself: a WebSocket bridge Grid opens, observes and sends turns through is not something "run this program with these arguments" can describe.
"permissions": {
"spawn": {
"bin": "copilot",
"argv": ["-C", "{cwd}", "-i", "{prompt}", "--session-id", "{sessionId}",
"--allow-all-tools", "--no-auto-update", "--no-remote", "--no-color"],
"resumeArgv": ["-C", "{cwd}", "--resume", "{sessionId}"],
"env": { "COPILOT_HOME": "{stateDir}/copilot-home" },
"sessionId": "mint-uuid",
"transcript": { "path": "{env.COPILOT_HOME}/session-state/{sessionId}/events.jsonl", "format": "jsonl",
"map": {
"user.message": { "role": "user", "text": "data.content" },
"assistant.message": { "role": "assistant", "text": "data.content" },
"tool.execution_start": { "role": "tool", "name": "data.toolName" }
} },
"hooks": { "file": "{env.COPILOT_HOME}/hooks/hooks.json", "shape": "claude-compatible",
"events": {
"sessionStart": "session-start", "agentStop": "stop",
"userPromptSubmitted": "user-prompt-submit", "preToolUse": "activity",
"postToolUse": "edit-write", "sessionEnd": "session-end",
"notification": "notification"
} },
"trust": "copilot-config"
}
}
bin is a bare program name, /^[A-Za-z0-9][A-Za-z0-9_-]*$/, at most 64 characters: no /, no \, no .. Core resolves it off PATH (or off an operator's own pinned absolute path in ~/.grid/config.json) the same way it already resolves claude and codex. A manifest cannot spell an absolute path itself: that would let a plugin point Grid at a file it shipped inside its own bundle, which is "run my payload" wearing "run the user's agent" as a disguise, and it would bypass the whole permission model, since Node's --permission flags fence the plugin's own process and never reach a program spawned outside it. Where the binary actually lives on this machine is the operator's fact to give, through the same path-pin control the two bundled providers already use. The plugin does not get to assert it.
argv takes up to 64 elements. (resumeArgv follows the same rules and is sent instead when picking a session back up. forkArgv follows them again and is sent instead of resumeArgv when the id is being resumed into a new conversation branched off it rather than continuing the same one, as in a card fork or a hand-off to a different session.) Each one is either a literal (/^-{0,2}[A-Za-z0-9][A-Za-z0-9._-]*$/, a flag or a plain word, and --foo=bar cannot be spelled at all) or a placeholder, drawn only from the closed vocabulary {prompt} {sessionId} {cwd} {model} {effort} {cardPath} {agentPerms} {permissionMode} {tools}. A placeholder must be the WHOLE argument: ["--dir", "{cwd}"] loads, and ["--dir={cwd}"] is refused outright, because argv is exec'd directly with no shell in front of it, and a value with a space in it, substituted into the middle of one element, would silently turn one argument into two. forkArgv is optional: leave it out and a provider that cannot fork simply cannot be forked.
Three of those placeholders carry more than a value. {agentPerms} is the one core has a side effect for, and naming it is what asks for it: the spawn engine mints this card's own permission file (the rows rebuilt at every spawn from the stage, the project's grid.toml and the thread's commit mode) and expands the placeholder to its path. The file is written in Claude Code's --settings shape, so a CLI that cannot read that shape has no business naming it, and a provider that leaves the placeholder out costs nothing and gets nothing. {permissionMode} is the thread's own permission mode, sent on the command line because a session billed to a non-primary account reads its settings from that account's own root, where a mode written into the primary's config does not reach it. {tools} is the stage's own comma-joined tool list when the workspace's Tools per stage rule is Lean, and has no value under Full. The flags that take a list like this are usually variadic, so put the pair where the next element is always a flag that never drops, never directly before the prompt.
The dropping rule. When core has no value for a placeholder, that argv element is removed outright, and so is the element immediately before it, when that element is a literal beginning with -. Nothing is ever substituted in its place: never an empty string, and never the literal text {model}. So ["--model", "{model}"] with no model resolved sends no --model flag at all, rather than --model ''.
Do not design an argv around this rule firing for {model} or {effort}. Both are filled from the workspace matrix (sessions.js agentConfigFor → settings.matrixDefault), and for an ordinary card in an ordinary stage the matrix resolves both, so the placeholder has a value and the flag IS sent. They are empty only when the matrix cell itself carries none. copilot-provider was first written on the opposite assumption, declared --model {model} expecting the flag to vanish, and a live session sent --model sonnet to a CLI that has never heard of it. If your provider's CLI must not receive a flag, leave the placeholder out of the argv rather than hoping it will be dropped. This rule exists because of a real, verified incompatibility: @github/copilot 1.0.82 refuses --model auto alongside --effort <level> ("Model "auto" does not support reasoning effort configuration"), and refuses --effort alone the same way, because Copilot's own default model is auto. An argv template that could not express "leave this flag out entirely" could not describe that CLI at all. copilot-provider's own argv names neither {model} nor {effort}. See Model and effort for the second reason, which is that Grid's model names are not Copilot's.
env takes up to 16 variables handed to the spawned process. Names must be uppercase (/^[A-Z][A-Z0-9_]*$/); PATH, SHELL, IFS, ENV, PS4, PAGER, EDITOR, VISUAL, CLASSPATH, JAVA_TOOL_OPTIONS, GEM_PATH, LOCPATH and HOSTALIASES are refused outright, and so is anything under the prefixes LD_, DYLD_, NODE_, BASH_, PERL, PYTHON, RUBY, GIT_ and GRID_. Every one of those changes what code a process loads or runs, rather than telling an agent where its own files live, and that line holds regardless of which provider is asking. Values may template over {stateDir}, {cwd}, {sessionId}, {cardPath}, {accountDir} and {accountId}, embedded directly in the string, since an env value has no argument boundary to break. {accountDir} is the config directory of the account this session bills to, and it is what binds a spawned agent to a Grid account: "CLAUDE_CONFIG_DIR": "{accountDir}" is the whole of it. It is empty on the primary account, and a value naming a placeholder this install has no value for is omitted ENTIRELY rather than set to an empty string, because no CLAUDE_CONFIG_DIR at all is a different login from one pointed at nothing. {accountId} is that account's id (default for the primary), for a manifest that would rather have a stable per-account subdirectory than the directory itself.
sessionId has one accepted value, "mint-uuid": Grid mints a v4 UUID before the spawn and hands it over, which is what makes {sessionId} usable in argv or in transcript.path. Leave the key out and the CLI is left to pick its own session id; {sessionId} cannot appear in argv without it, or nothing would ever decide what it expands to.
trust is claude-json or copilot-config: whose folder-trust prompt Grid answers for the working directory it is about to hand over, before the first turn. That write is a merge into another program's config file rather than an argv, so a manifest cannot describe it and names one core already knows how to make instead, the same way hooks.shape names a file layout the installer knows how to write. The boundary stays core's: only a directory the person themselves put into Grid (the project's own folder, a worktree Grid cut, or the workspace root) is ever asserted trusted, and the answer is written into that account's own config root, leaving your repository alone. Leave the key out and the agent's own dialog stands wherever it would have stood.
permissions.spawn.transcript is how Grid reads a provider's own words back into a card, with no code in core that knows that provider's event shapes ahead of time:
path is a template that must start with {stateDir}, {cwd} or {env.NAME} (naming a variable this same block's own env sets), so it can never begin with a literal /, and no segment of it may be ... A transcript is rendered straight into a card, so a plugin that could root that path anywhere on disk would be choosing an arbitrary file for Grid to read aloud. Within the template, {sessionId}, {accountDir} and {accountId} may appear as well, so a manifest can give each account its own transcript subtree; neither account placeholder may be a root, because {accountDir} is empty on the primary account and a root that can be empty is a path that can point at {stateDir}'s own parent.format is "jsonl" today: one JSON object per line, one event per object.event is a dotted selector naming the field that carries a line's event type, so map has something to key on. It defaults to "type" when left out. copilot-provider leaves it out, because Copilot's own events already arrive as {"type": "user.message", "data": {...}}.map takes up to 32 entries, event type → { role, text, name }. role is a literal word from user | assistant | tool | system, which is Grid's own transcript vocabulary rather than the provider's. text and name are dotted selectors into the event, like data.content. copilot-provider maps user.message and assistant.message to a role plus text: "data.content", and tool.execution_start to role: "tool" plus name: "data.toolName".permissions.spawn.hooks names the file Grid merges its own hook entries into, so a workspace's Stop- or SessionStart-triggered automation keeps firing under a third-party agent as well as under Claude:
file follows the same rooted-path rule as transcript.path.shape is "claude-compatible" today: { hooks: { <Event>: [{ matcher?, hooks: [{ type: "command", command }] }] } }, the exact shape hookinstall.js already merges into settings.local.json.events takes up to 32 entries, and the direction is the one thing to get right: the key is the AGENT's own event name, and the value is one of GRID's, drawn only from the closed set session-start, user-prompt-submit, notification, stop, session-end, ask-user-question, answered-question, edit-write and activity. copilot-provider maps its own sessionStart, agentStop, userPromptSubmitted, preToolUse, postToolUse, sessionEnd and notification onto Grid's session-start, stop, user-prompt-submit, activity, edit-write, session-end and notification respectively.A provider that declares no hooks block still works, degraded, off its transcript alone. Grid still reads what it said and renders it into the card, but nothing fires the instant a stage-relevant event happens inside the agent's own process. hooks is what turns transcript polling into a push, and a provider runs without it.
capabilities sits beside permissions at the top of the manifest. It is a claim rather than a grant: two keys, signed with everything else in the manifest, that let core act sensibly on a provider it knows nothing else about.
isolation is env, filesystem or none: the provider's own answer to whether two of its accounts can run on one machine without one credential clobbering the other. copilot-provider declares none, which is the machine-readable form of the paragraph above about Copilot having no second account: with it declared, a spawn that would put a second live session on that provider is refused with a waiting: line saying so, rather than quietly sharing a login. Leave it out and nothing is degraded. Absence means nobody has answered the question, and core reads that as "assume nothing is wrong".errors is the provider's own failure text mapped onto the four words core acts on: rate_limited, auth_failed, fatal and retryable, each a list of regular expressions matched against whatever the program printed before it died, first hit in that order winning. A rate_limited verdict holds that account back until the reset the text named (or five minutes, when it named none) and an auth_failed one until someone signs in again, so an account can be taken out of rotation by a failure core cannot read for itself.The block is read generically: there is no provider id anywhere in the code that acts on it, so a manifest declaring "isolation": "none" degrades for the whole class of providers that cannot isolate rather than for a name core recognises.
No code from a spawn contract ever runs inside the session's own process, the daemon, or anywhere in between: core reads it as data and never calls back into it. Concretely:
cost.js derives dollars from Claude Code's own transcripts, at published API rates; a session on any other provider is counted unpriced rather than zero-cost. Codex has had this same gap since it shipped, and Copilot inherits rather than closes it (see what did not change below)./btw side questions, backlog refinement and the design chat all spawn claude directly (titler.js, aside.js, and askmodel.js for refinement and design) instead of the card's own provider. The capture chips and project discovery are the exception: they follow the workspace's provider. A workspace running every card through Copilot still gets Claude-written titles, on whichever Claude account is signed in on that machine.A spawn permission rides inside the signed manifest, so installing or updating the plugin shows it in the same grid plugin install printout as network, filesystem and shell, with a line that says plainly the program it names "runs OUTSIDE the plugin sandbox, as you, with all of your rights." An update that widens what is declared is refused until someone acknowledges it: a changed bin, a new argv element, a new env var, anything at all in the block counts as a widening. A manifest cannot quietly turn copilot into something else between one version and the next.
That gets the plugin installed. It does not get its binary run. The first time a person actually picks that provider on Settings › Agents and models, Grid asks a second, narrower question: the RESOLVED path on this machine (not the bare name the manifest asked for), the exact argv it is about to run, and the same "outside the sandbox, as you, with all of your rights" sentence. Only a person saying yes there records the approval. Until it is given, the row is drawn but not selectable, and a spawn attempted anyway is refused by a belt-and-braces check inside the session engine, with the same reason. That second check asks whether Grid itself shipped and signed this plugin, reading the install source and the Ed25519 signature re-derived from the bytes on disk rather than the plugin id, which is only a directory name anyone may pick. A plugin Grid ships and signs starts without a second answer, because whether Grid may run the agent Grid shipped is a question answered by installing Grid; a third party's plugin, and a bundled one whose signature is broken or missing, is refused until the approval is there. The approval is keyed to the plugin's live content hash rather than to its id: change one byte on disk (the argv, the binary name, anything) and the stored approval no longer matches what pluginhost recomputes at boot, so it is asked for again, exactly like a widened install.
The spawn contract itself is shipped, above. Nothing about it is still to come. Gemini CLI, Ollama and private endpoints on Fleet are follow-on plugins nobody has written yet; nothing in core blocks them from being written today.
Copilot sessions are not priced and show no usage meter: cost.js and usage.js both read Claude's own transcripts and Claude's own statusline, and neither reads a plugin-declared transcript block. Codex has had the identical gap since it shipped. Naming that gap here is this section's job. Closing it, by teaching cost and usage accounting to read any provider's transcript and not only Claude's, is a follow-up that the spawn contract does not already do.
"Works with Claude Code" and "works with Codex CLI" are statements of compatibility, nothing more. Grid Console is not affiliated with any provider, handles no provider login, and routes no model calls of its own.