# Run an agent without the dashboard

> Take an AI agent from no credential to a confirmed change, entirely from a terminal and the agent itself.

Canonical page: https://anectico.com/docs/agents/zero-dashboard-quickstart/


This is the whole loop — connect, discover, investigate, act — without opening the product. Nothing
below asks you to click anything: you mint a credential from the terminal, and the agent finds its
own way around from two entry-point tools.

One browser step remains and it is a sign-in page rather than the product: `anectico login` runs a
device-code flow. You can skip even that by exporting an existing key as `ANECTICO_API_KEY` — but it
must hold `api_key:write` **and every scope you are about to grant**, because a key can never mint a
scope its creator lacks. Signing in is simpler precisely because your org role supplies them.

That first `anectico login` is also the explicit provisioning/linking step required before a direct
OAuth MCP connection can represent you. An OAuth access token alone never creates an Anectico
workspace or membership. If you are joining someone else's workspace, have an owner invite your
address with `anectico invitations create person@example.com --role ROLE_MEMBER`, then accept that
invitation after signing in; this still requires no visit to the dashboard.

## 1. Mint the agent a credential

```bash
anectico login
anectico projects list
anectico agent bootstrap --project <project-id> --host claude
```

`anectico agent bootstrap` mints a narrow, project-scoped, expiring key, stores the secret in your
OS keychain, writes the host's MCP configuration, and connects with the **new** key to check what it
can see. `--host` accepts `claude`, `codex`, `cursor`, `gemini`, or `none`; only `claude` and
`cursor` have a config file this command writes, and the others get printed instructions instead.
See [MCP client compatibility](/docs/reference/mcp-compatibility) for what each host needs.

By default it grants the `investigate` bundle — **20 scopes, read-only, no mutation at all** — and
expires in 30 days. Every one of those 20 is a scope an ordinary member holds, so this step needs no
special privilege. A **viewer** cannot run it: minting a key is itself privileged, and the refusal
arrives before the scope list is read.

## 2. Put the key where the agent will read it

The generated configuration references `ANECTICO_API_KEY` rather than embedding the secret, so the
file stays safe to commit. Nothing sets that variable yet:

```bash
eval "$(anectico agent key env --export)"
```

Until you do this the agent cannot authenticate, and the bootstrap report says so — the step appears
as `bridge` with the status `manual`, so a successful run never implies the agent is ready.

## 3. Confirm the connection without invoking a tool

```bash
anectico mcp verify
```

It identifies the server over the current protocol and reports which discovery path answered. A key
that authenticates but sees **zero tools** is a failure, not a quiet success: `mcp:read` on its own
is served an empty tool list, because every tool also requires the scope of the domain it reads.

## 4. The agent's first two moves

Two tools are the way in:

- **`search`** takes one query and looks across people, error issues, log lines and business
  accounts in a single call.
- **`fetch`** takes one reference from a search hit — or from any tool's `evidence` array — and
  returns the object it names.

```json
{"name": "search", "arguments": {"query": "buyer@acme.example", "since": "24h"}}
```

The answer is a list of hits and a per-source report. Each hit carries `ref` (a canonical
`anectico://…` reference), `kind`, `fetchable`, and `tool` — the specialised tool that produced it
and the place to go for real filters. Each source reports `searched`, `unauthorized` (with the scope
that was missing), `out_of_profile`, `not_requested`, or `failed`, so an empty answer is never
ambiguous: the agent can tell "there is nothing there" from "this key may not look".

```json
{"name": "fetch", "arguments": {"ref": "anectico://person/1f0c…"}}
```

`fetch` resolves references of kind `person`, `error_group` (an issue), `trace`, `incident`,
`agent_run` and `account`, and returns exactly what that object's own read tool returns. The
reference is its only argument — it already carries the project when the object needs one. Holding a
reference grants nothing: every fetch is authorized against the key exactly as the original read
was.

**Only a hit marked `fetchable: true` can be passed to `fetch`** — check the flag rather than
assuming, because a cited reference is not automatically a fetchable one. A log line is the case an
agent meets first: it is a `search` source and it is cited everywhere, but it has no single-object
read. The refusal says what to do instead, per kind:

```text
fetch cannot resolve a "log" reference. A log line has no single-object read:
find it with search_logs, or fetch the trace its citation carries.
```

**Traces are deliberately not a `search` source.** They are found by filter (`search_traces`) and by
pivot — a log hit carries its trace reference, and `fetch` resolves it.

## 5. Go deep with the specialised tool the answer named

`search` and `fetch` add no query power of their own. Once the agent knows what it is looking at, it
uses the tool the hit named, with that tool's filters:

```json
{"name": "get_person_timeline", "arguments": {"id_param": "buyer@acme.example", "since": "24h"}}
{"name": "search_logs", "arguments": {"level": "error", "trace_id": "4bf92f35…", "since": "1h"}}
{"name": "get_issue", "arguments": {"group_id": "grp_…"}}
{"name": "get_affected_users", "arguments": {"group_id": "grp_…"}}
```

Every result carries an `evidence` array of citations, each with a `ref` and RFC 6901 pointers into
the data it supports, so the agent can quote a specific row rather than the whole answer — and you
can `fetch` anything it quoted.

Administrative reads — on-call schedules, delivery receipts, ticket links, saved searches, account
lookup, product and export definitions, plan usage, LLM cost — are not first-class tools. Call
`list_read_actions` with no arguments to browse them by domain, pass a `domain` or `query` to get
each one's argument schema, then run it with `execute_read_action`. Both are read-only.

## 6. Mint a second key, this one able to act

An `investigate` key cannot mutate anything — it holds no `mcp:write`, so no write tool is served to
it at all. Resolving an issue needs four scopes: `mcp:read` and `errors:read` to see the issue and
its current version, `mcp:write` and `errors:write` to change it.

**You must be an owner or admin to do this.** `mcp:write` is admin-granted, and a key can never carry
a scope its creator lacks, so a member's mint is refused by name.

Name those four scopes and nothing else. `--scope-profile ""` is the part to get right: omit it and
the bundle default applies, and you quietly mint a *second* read-only `investigate` key that cannot
do the job either.

```bash
anectico agent bootstrap --project <project-id> --host claude \
  --scope-profile "" \
  --scope mcp:read --scope mcp:write \
  --scope errors:read --scope errors:write
```

That reports `Scopes: 4` and the tools the new key can see. Now put the **new** key in the
environment, replacing the read-only one from step 2:

```bash
eval "$(anectico agent key env --export)"
```

**Then restart the agent, or reconnect the server in it.** The tool list is decided by the credential
that authenticated, and a host that connected with the read-only key holds a read-only tool list until
it connects again. This is the step people skip, and its symptom is "the server does not support
writes" from a key that supports writes perfectly well.

With the write-capable key connected, the agent discovers mutations through `list_write_actions` and
runs one through the door its reach decides: `execute_internal_action` for a change that stops inside
your workspace, `execute_external_action` for one that also causes something Anectico cannot retract.

```json
{"name": "list_write_actions", "arguments": {"domain": "errors", "destructive": true}}
```

A few daily-driver mutations are promoted to their own tools so a host can tell an acknowledgement
from a delete. Resolving an issue is one of them, and it previews first:

```json
{"name": "set_issue_status",
 "arguments": {"group_id": "grp_…", "action": "resolve",
               "expected_version": 7, "note": "fixed in 4.2.1"}}
```

The first call **applies nothing**. It returns a preview of what would change plus a
`confirm_token`. Repeat the identical call with that token to apply it:

```json
{"name": "set_issue_status",
 "arguments": {"group_id": "grp_…", "action": "resolve",
               "expected_version": 7, "note": "fixed in 4.2.1",
               "confirm_token": "…"}}
```

The token binds the organization, the caller, the tool and the exact arguments previewed, and lasts
10 minutes. Change one argument and it stops matching — preview again.

Read the preview before confirming. It is the last place some information exists: resolving an issue
replaces the whole lifecycle overlay and a later call reverses the status only, so the resolution
note, resolver, release baseline and regression state are named in the preview and nowhere
afterwards. Resolving also reaches outside Anectico — it closes the linked ticket in your own
tracker.

A committed write returns a **receipt**: the operation, the outcome, what changed, and the targets the
platform confirmed — plus `effect_scope` and `destructive`, so a record of what the agent did says how
far it went. It carries a mutation id and a commit time when the platform reports them, and null when
it does not; null there means "re-read to find out", not "nothing happened".

## 7. Verify the loop

```bash
anectico issues list --status resolved
```

The issue is now `resolved`, its version has advanced, and the note you passed is on it. The same
change is in the audit trail under **Settings → Audit**, naming the tool, the resource and the
outcome — including the preview that preceded it.

If the loop stopped somewhere, `anectico mcp verify` identifies the server and confirms the current
key reaches it; and remember that a key that authenticates but sees **zero tools** is a scope
problem, never a server problem.

## Where to go next

- [Scope recipes for agent jobs](/docs/agents/scope-recipes) — the minimum scope set per job.
- [How agent access is enforced](/docs/agents/security-model) — what a scope grants, what a profile
  does and does not decide, and what "destructive" means here.
- [MCP tool reference](/docs/reference/mcp-tools) — every tool and action with its required scopes.
- [Revoke an agent's access](/docs/agents/revoke-agent-access) — and what is already out of reach.
