# Author and publish a managed agent

An agent is one Markdown document: optional YAML frontmatter holds settings,
and the body holds instructions and resource mentions. The server is the only
parser and compiler. CLI, SDK, and Studio send the exact document; do not calculate
a digest or reconstruct the document from a parsed object.

Start with the no-connection support example below. Shared app and MCP connections
are available when configured for your project. Shared secrets require the
managed sandbox secrets capability. Test saved drafts in Studio before publishing.
Signed-in users can connect personal accounts. Agent schedules follow the Live version at each occurrence.

## Install the authoring skill

Download the [authoring skill](https://sikaru.ai/skills/author-sikaru-agent/SKILL.md)
into your coding agent's skill directory. For a project-local Codex installation:

```sh
mkdir -p .agents/skills/author-sikaru-agent
curl --fail --output .agents/skills/author-sikaru-agent/SKILL.md \
  https://sikaru.ai/skills/author-sikaru-agent/SKILL.md
```

The [frontmatter JSON Schema](https://sikaru.ai/schemas/agent-document.json)
validates the parsed YAML object, not the entire Markdown file. Server validation
also checks resource availability, matching mentions, model choices, project
ceilings, and the combined 100,000-character instruction limit.

## First agent using only the CLI

Install the published Sikaru CLI and sign in. The [CLI guide](https://docs.sikaru.ai/sdks-and-cli/cli)
lists every install option.

```sh
curl -fsSL https://app.sikaru.ai/install.sh | sh   # or: npm install -g @aadi-labs/sikaru-cli
sikaru login --no-browser                          # new users are signed up; saves the default project
```

The user approves the printed code in their own browser. Agents without the CLI can
follow https://sikaru.ai/auth.md instead. For scripts and CI, have `jq`, a real
project ID, and a server-side `SIKARU_API_KEY` available instead of the sign-in. The key
needs managed-agent write access and the run permissions required for invocation.
Keep credentials out of files and copyable examples. Use `sikaru agent --help`
to inspect the document authoring commands.

1. Save the support example as `agent.md`, or download it:

```sh
curl --fail --output agent.md https://sikaru.ai/examples/agents/support.md
export PROJECT_ID='your-real-project-id'
```

2. Create the agent from the file. The CLI sends the exact text, including
trailing newlines. Choose an unused slug; a conflict means inspect the existing
agent, not overwrite it.

```sh
sikaru agent create support -p "$PROJECT_ID" --file agent.md --name Support
```

3. Pull and validate the draft. Pull writes `agent.md` and returns its revision.

```sh
sikaru agent pull support -p "$PROJECT_ID" --file agent.md
sikaru agent validate support -p "$PROJECT_ID" --file agent.md
```

For later edits, use `sikaru agent push support -p "$PROJECT_ID" --file agent.md
--revision REVISION` with the revision you actually pulled.
Push saves an existing agent; it never creates one. Save conflicts return the
newer draft so you can reconcile. Do not blindly retry with its revision.

4. Review the validation and intended access. Obtain a review of the first saved
draft (revision 1 for the untouched example), inspect its diff and access delta,
then publish after authorization using that review's digest:

```sh
sikaru agent review support -p "$PROJECT_ID" --revision 1 > review.json
# Inspect review.json and obtain authorization before publishing.
sikaru agent publish support -p "$PROJECT_ID" \
  --revision 1 --access-digest "$(jq -r .accessDigest review.json)" --acknowledge-widening
```

Built-ins are enabled by default except settings explicitly disabled in the
document. The first publication acknowledges those capabilities as well as named
connections. For later publication pass the current `--live-version VERSION_ID`,
current revision, reviewed access digest, and the acknowledgements warranted by the reviewed delta.
Use the public document review API (`POST /v1/projects/{project_id}/managed-agents/{agent_slug}/document/review`)
to inspect the diff before a change. Its publication schema accepts
`expectedAccessDigest` to bind publication to that exact review. The CLI exposes
this through `sikaru agent review` and `sikaru agent publish --access-digest`.
Refetch and inspect review if access changes; never automatically accept a new digest.

5. Run the published agent. `sikaru agent run` creates a hosted execution session,
sends one turn, and streams events; without a task it opens a conversation in the
terminal. Open the agent's Conversations to see its Live session.

```sh
sikaru agent run support -p "$PROJECT_ID" "Hello"
```

Tools run on Sikaru by default. Add `--compute local` to run them in your own
workspace instead; see [Local compute](https://docs.sikaru.ai/run/local-compute).

## Worked documents

These examples are structurally valid. The app and MCP examples additionally
require matching shared project connections and available tools before publication.
Use the actual resource slug from Connections, not a display name guessed from prose.

### Support: no connections required

```markdown
---
budget:
  max_calls: 20
  max_tokens: 12000
tools:
  bash:
    enabled: false
---
# Support guide

Explain the policy supplied in the conversation. State what is missing before
making a recommendation. Ask before taking an external action.
```

### Shared app: connect HubSpot first

```markdown
---
access:
  "@app/hubspot":
    ownership: shared
    policy: require_approval
---
# Account brief

Use @app/hubspot to summarize the requested account. Cite the records you used.
Ask the user to review any proposed change before writing it.
```

### Shared MCP: connect research with its search tool first

```markdown
---
access:
  "@mcp/research":
    ownership: shared
    tools: [search]
    policy: allow
web:
  allow_domains: [example.com]
---
# Research assistant

Use @mcp/research to search the connected collection. Explain uncertainty and
cite the results. Do not invent documents that were not returned.
```

## Mentions and access

Mention syntax is `@app/slug`, `@mcp/slug`, `@secret/slug`, `@skill/slug`, or
`@agent/slug`. A slug starts with an ASCII letter or digit and continues with
letters, digits, underscores, or hyphens. Resource names are case-sensitive.
Mentions in fenced or inline code and escaped mentions do not grant access.
A YAML access entry must have a corresponding body mention. Adding access in
Studio inserts a mention; removing the mention removes access on publication.

Each access entry accepts `ownership`, `tools`, and `policy` (`allow`,
`require_approval`, or `deny`). Omitting `tools` selects the connection's current
catalog and pins it at publication; explicit `tools: []` selects no tools. Use an
explicit tool list to narrow access. `deny` remains a denial, not an approval
prompt. App connections and secrets support `shared` or `personal` ownership;
MCP connections use shared ownership. Personal ownership resolves the signed-in
runner's own account, never another member's account.
The validator reports resolved-shared, resolved-personal, unresolved, ambiguous,
broken, or blocked. Unresolved, ambiguous, broken, and blocked resources need
attention before Go live. An intentional changed tool definition must be reviewed
and explicitly acknowledged to pin the new definition. Revoked tools never
silently fall back to another connection or search provider.

Built-ins are web search, web fetch, code execution (`bash`), workspace files
(`workspace`), memory, and subagents (`agents`). All default on within project
ceilings. Frontmatter also supports `model`, `budget`, `web`, `setup`, and embedded
`sources`; consult the schema. The server refuses unknown keys. A schema pass
alone is not permission to use a resource.

## Draft, Go live, and suggested changes

Saving a draft leaves live behavior unchanged. Go live publishes a new version;
removing or widening access requires explicit confirmation. Existing sessions
keep their pinned instructions, but removed access stops on their next turn.
Versions record author and time; review a diff before reverting and confirm the
real revert operation. A stale save or publication is refused.

Evolve and Improvements may suggest prose changes. They cannot add or remove
mentions or change access settings. Accepting a suggestion creates a labelled
suggested draft, never overwrites your draft, and never goes live directly.
Adopt or dismiss it, then review and publish through the same human Go live flow.

Shared conversations show messages and tool calls to project members; model
reasoning is hidden and secrets are redacted. Once a session uses a personal
connection or secret, its entire conversation is readable in full only by its
runner. Other members see its status and usage. A project-key request's `user_id`
is a caller label and does not authenticate a Sikaru user.

## Secrets and personal accounts

Never put a real secret in frontmatter or instructions. Create a shared or personal secret in
Connections with a write-only value and exact allowed hosts, then mention its
slug, for example `@secret/apollo`. Validation remains blocked if managed sandbox
secrets are not configured. A local compute session receives no placeholders and
reports that secrets need the managed sandbox.

In managed sandbox code, read the nonsecret placeholder by its exact slug:

```python
import json
import os
import requests

placeholder = json.loads(os.environ["SIKARU_SECRET_PLACEHOLDERS"])["apollo"]
response = requests.get(
    "https://api.apollo.io/v1/example",
    headers={"Authorization": f"Bearer {placeholder}"},
    allow_redirects=False,
)
response.raise_for_status()
```

The placeholder stays stable within a session, including after value rotation.
Only request headers to currently allowed HTTPS hosts receive the real value.
Other destinations are refused, redirects are not followed, and echoed values
are redacted from response headers and streaming bodies. Body substitution is
not supported. Removing a mention, revoking its connection, or narrowing hosts
removes that reach from existing sessions too. A `require_approval` secret pauses
each outbound request for its own decision in the ordinary pending-actions and
approval APIs; denying that request never reveals the value. Approval does not
grant later requests.

Personal connections belong to each signed-in runner; missing accounts prompt the runner to connect and resume. Scheduled calls use the authority described below. Channels and inbound webhook triggers are outside this authoring flow.

## Test a saved draft

In Studio, enter a Test message and choose **Test current draft**. Studio saves
pending edits and pins that exact revision. Review and acknowledge any added or
changed access. The test runs as the signed-in tester, appears in Conversations
labelled **Draft**, and keeps memory and workspace files separate from Live.
Testing never activates the candidate or changes existing Live instructions.
Unresolved, broken, or ceiling-blocked mentions name the issue and refuse the test.
Authorized project members share access to shared connection slots. Personal
accounts resolve as the signed-in tester. A missing account pauses the turn so
the tester can connect and resume; a project-key run fails that personal call
with `needs_signed_in_user` and continues without borrowing an account.

From the public API, create an execution session with `environment: "draft"` and
`draft_revision` set to the saved revision. Set `acknowledge_widening: true` only
after reviewing its access changes. Pass the review's `accessDigest` as
`expected_access_digest` to refuse a changed tool or host snapshot. Project-key requests still require `tenant_id`
and `user_id`; those labels do not authenticate a person. Then append a turn and
read its events as for a Live session. A stale revision returns HTTP 409.

The separately installed authoring companion tests the same saved document:

```sh
sikaru-authoring dev support --project PROJECT_ID --revision 2 \
  --tenant customer-123 --user user-123 --prompt "What changed?"
```

Add `--acknowledge-widening` after reviewing added or changed access. `dev` accepts
an existing agent slug and saved revision; it creates no temporary agent. Use the
server document import operation to turn files into a document, then save that
exact document through the revision-checked authoring workflow above. `init` and
`check` retain their existing directory manifest interface.

## Hosted invocation snippets

These examples are generated from the same source as Studio and `agent snippets`.
Replace `PROJECT_ID` with your project. The agent slug here is `support`; fetch
snippets for your actual agent after Go live. No example contains an API key.

### cli

```sh
export SIKARU_API_KEY='YOUR_API_KEY'
sikaru agent run support -p PROJECT_ID Hello
```

### curl

```sh
export SIKARU_API_KEY='YOUR_API_KEY'
set -e
SESSION=$(curl --fail-with-body -sS https://api.sikaru.ai/v1/projects/PROJECT_ID/harnesses/support/execution-sessions -H "Authorization: Bearer $SIKARU_API_KEY" -H 'Content-Type: application/json' -d '{"tenant_id":"customer","user_id":"user"}' | jq -r '.session.id')
RUN=$(curl --fail-with-body -sS "https://api.sikaru.ai/v1/projects/PROJECT_ID/execution-sessions/$SESSION/turns" -H "Authorization: Bearer $SIKARU_API_KEY" -H 'Content-Type: application/json' -d "$(jq -n --arg key "$(uuidgen)" '{idempotency_key:$key,input:{message:"Hello"}}')" | jq -r '.run.id')
curl --fail-with-body -N "https://api.sikaru.ai/v1/projects/PROJECT_ID/runs/$RUN/events/stream" -H "Authorization: Bearer $SIKARU_API_KEY"
```

### python

```python
import os
from uuid import uuid4
from sikaru_sdk import SikaruApi

client = SikaruApi(api_key=os.environ["SIKARU_API_KEY"])
session = client.execution_sessions.create(project_id="PROJECT_ID", harness_id="support", tenant_id="customer", user_id="user")
turn = client.execution_sessions.append_turn(project_id="PROJECT_ID", session_id=session.session.id, idempotency_key=str(uuid4()), input={"message": "Hello"})
for event in client.runs.stream_events(project_id="PROJECT_ID", run_id=turn.run.id):
    print(event)
```

### typescript

```typescript
import { SikaruApi } from "@aadi-labs/sikaru-sdk";
import { randomUUID } from "node:crypto";

const client = new SikaruApi({ apiKey: process.env.SIKARU_API_KEY! });
const session = await client.executionSessions.create("PROJECT_ID", "support", { tenant_id: "customer", user_id: "user" }) as { session: { id: string } };
const turn = await client.executionSessions.appendTurn("PROJECT_ID", session.session.id, { idempotency_key: randomUUID(), input: { message: "Hello" } }) as { run: { id: string } };
for await (const event of await client.runs.streamEvents("PROJECT_ID", turn.run.id)) {
  console.log(event);
}
```

## Existing directory authors

The explicit `sikaru.json` file-tree manifest remains supported by the separately
installed `sikaru-authoring` companion and Python `sikaru_authoring.compile_directory`.
It packages only listed files and rejects symlinks, traversal, duplicates, and
invalid definitions. Use that existing contract for directory integrations; do
not pass a directory digest as an agent document. Document agents use the single
file workflow above. Legacy directory release changesets retain their existing
review and staging/production operations; document suggestions require Go live.

## Troubleshooting

- Unknown key or invalid YAML: fix the line and column reported by validation.
- Unresolved or ambiguous mention: connect or choose the exact resource in Connections.
- Broken mention: reconnect, or review a changed tool definition before republishing.
- Blocked capability: inspect the project's ceiling and available capabilities.
- HTTP 409: read the current draft/live version and reconcile; never force an overwrite.
- HTTP 402: check subscription and spending limits. Signing in again does not fix billing.
- HTTP 503 or interrupted create: inspect the existing slug and receipt before retrying;
  do not create replacement identities to bypass an uncertain result.
- No events yet: creation alone is not a completed turn. Inspect the returned run and
  stream status before claiming the agent answered.

## Schedule the live agent

Create an agent schedule with `POST /v1/projects/{project_id}/run-schedules`:

```json
{
  "agent_slug": "support",
  "environment": "production",
  "cron": "0 9 * * MON-FRI",
  "timezone": "America/Los_Angeles",
  "session_mode": "fresh",
  "input": {"goal": "Summarize today's work."},
  "idempotency_key": "support-weekday-summary"
}
```

Choose `cron` with an IANA timezone, or `interval_seconds` (60 through 2592000).
The default `fresh` mode starts a conversation each time. `persistent` continues
one conversation and adopts the live document, model and access at its next turn.
A running or not-yet-delivered previous occurrence causes a recorded overlap skip.
Missed times coalesce into one occurrence, then the next future time is scheduled.

List with `GET /v1/projects/{project_id}/run-schedules?agent_slug=support`.
Each schedule includes its next run time. Read `/{schedule_id}/occurrences` for
admitted and skipped times, exact version and sealed access; use `nextBefore` as
`before` to retrieve older entries. Read `/{schedule_id}/notices` for actionable
owner notices. Pause or resume with `PATCH /{schedule_id}` and `{"paused": true}`
or `{"paused": false}`; delete with `DELETE /{schedule_id}`. Occurrence receipts
remain after deletion. The Studio offers these same operations under Triggers.

A signed-in creator uses personal connections only when they published the current
live version. A different publisher produces `needs_schedule_owner_confirmation`;
a missing account produces `personal_connection_missing`. Those tool calls fail,
the creator receives a notice, and the rest of the run continues. Project-key
schedules never acquire a human identity or borrow the publisher's account.
Confirmed membership removal pauses a human creator's schedule and records an
administrator notice. An unavailable membership service defers admission.

Existing unsigned API schedules using `session_id` retain their pinned session contract.
Sessions carrying a signed-in identity must use an `agent_slug` schedule so each
occurrence receives current membership and personal-access checks. Retained
identity-bearing session schedules pause with a notice instead of running.

## Conversation channels

Managed agents can start with HTTP or a configured Slack room/personal DM. HTTP is
the default for older creation clients. Slack setup lives in Settings → Channels;
tool Connections remain separate. A channel never grants personal connection
authority. Publication and explicit channel activation are separate actions. See
[Managed-agent channels](managed-agent-channels.md) for setup and lifecycle.
