> This page is also available as Markdown: append .md to its URL. The full index of Sikaru docs is at https://docs.sikaru.ai/llms.txt. Code examples use only operations from the public API reference at https://docs.sikaru.ai/api-reference.

# 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 rebuild the document from a parsed object.

Agent document workflow on this page (CLI unless noted):

1. Create: `jq -n --rawfile document agent.md '{agentSlug:...,displayName:...,source:{sourceKind:"agent_document",document:$document}}' | sikaru managed-agents create_managed_agent --project-id ... --json -`. A slug conflict means inspect the existing agent; never overwrite.
2. `sikaru agents pull --project ... --agent ... --file agent.md` writes the file and returns its revision. `sikaru agents validate ...` validates it.
3. `sikaru agents push --project ... --agent ... --file agent.md --revision REVISION` saves an existing agent (never creates one). Save conflicts return the newer draft.
4. `sikaru agents review --project ... --agent ... --revision N > review.json`, then `sikaru agents publish ... --revision N --access-digest <accessDigest> --acknowledge-widening`. Later publications also pass `--live-version VERSION_ID`. API: `POST /v1/projects/{project_id}/managed-agents/{agent_slug}/document/review`; publication accepts `expectedAccessDigest`.
5. `sikaru agents snippets --project ... --agent ...` returns hosted invocation snippets (create execution session, append turn, stream events).
6. Draft test via API: create an execution session with `environment: "draft"`, `draft_revision`, optional `acknowledge_widening: true`, `expected_access_digest`; project-key requests need `tenant_id` and `user_id`. Stale revision returns HTTP 409. Companion: `sikaru-authoring dev <slug> --project ... --revision N --tenant ... --user ... --prompt ...`.
7. Schedules: `POST /v1/projects/{project_id}/run-schedules` (`agent_slug`, `environment`, `cron` + `timezone` or `interval_seconds` 60–2592000, `session_mode` `fresh` or `persistent`, `input`, `idempotency_key`); list `GET .../run-schedules?agent_slug=...`; `/{schedule_id}/occurrences` (page with `nextBefore` as `before`), `/{schedule_id}/notices`; `PATCH /{schedule_id}` with `{"paused": true|false}`; `DELETE /{schedule_id}`.

Mention syntax: `@app/slug`, `@mcp/slug`, `@secret/slug`, `@skill/slug`, `@agent/slug`. Access entry fields: `ownership` (`shared`/`personal`), `tools`, `policy` (`allow`, `require_approval`, `deny`). Instruction limit: 100,000 characters combined.
Error codes: `needs_signed_in_user`, `needs_schedule_owner_confirmation`, `personal_connection_missing`; HTTP 409 (stale), 402 (billing), 503 (uncertain create).

```mermaid
flowchart LR
    Doc["agent.md"] --> Create["Create agent"]
    Create --> Draft["Saved draft (revision)"]
    Draft --> Validate["Validate"]
    Validate --> Test["Test current draft"]
    Validate --> Review["Review diff and access"]
    Review --> Live["Go live (publish)"]
    Live --> Invoke["Invoke: session, turn, events"]
    Live --> Schedule["Schedule"]
```

You save a document as a draft revision, validate it, and optionally test it. You then review its diff and access, and publish it with Go live. The Live version is what invocations and schedules use.

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.

## Create your first agent with the CLI

Install the Sikaru CLI using the current [CLI guide](https://docs.sikaru.ai/sdks-and-cli/cli). Have `jq`, a real project ID, and a server-side `SIKARU_API_KEY` available. The key needs managed-agent write access and the run permissions required for invocation.

> **Note**
>
> Keep credentials out of files and copyable examples. These commands require a CLI version exposing `sourceKind: agent_document` and the native `agents` subcommands. Check `--help` before you continue if your installed version is older.

#### Get the example document

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'
```

#### Create the agent identity

Use the generated public operation. `jq --rawfile` keeps the exact text, including trailing newlines. Choose an unused slug. A conflict means inspect the existing agent, not overwrite it.

```sh
jq -n --rawfile document agent.md \
  '{agentSlug:"support",displayName:"Support",source:{sourceKind:"agent_document",document:$document}}' \
  | sikaru managed-agents create_managed_agent --project-id "$PROJECT_ID" --json -
```

#### Pull and validate the draft

Pull writes `agent.md` and returns its revision.

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

For later edits, use `sikaru agents push --project "$PROJECT_ID" --agent support --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.

#### Review, then publish

Review the validation and intended access. Get 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 agents review --project "$PROJECT_ID" --agent support --revision 1 > review.json
# Inspect review.json and obtain authorization before publishing.
sikaru agents publish --project "$PROJECT_ID" --agent support \
  --revision 1 --access-digest "$(jq -r .accessDigest review.json)" --acknowledge-widening
sikaru agents snippets --project "$PROJECT_ID" --agent support
```

#### Run the returned CLI snippet

It creates a hosted execution session, appends a turn, and streams events. Open the agent's Conversations to see its Live session. Keep the returned session ID to continue that conversation.

`sikaru exec` attaches local compute and is not this hosted invocation workflow.

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.

> **Warning**
>
> Check your installed client schema for `expectedAccessDigest` and the review operation before relying on them; older generated clients may not expose them yet. The native `review` and `publish --access-digest` commands require a client generated from this contract. Refetch and inspect review if access changes. Never automatically accept a new digest.

## Worked documents

These examples are structurally valid. The app and MCP examples also 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 matching body mention. Adding access in Studio inserts a mention; removing the mention removes access on publication.

Each access entry accepts these fields:

| Field       | Values and behavior                                                                                                                                                                           |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ownership` | `shared` or `personal` for app connections and secrets. MCP connections use shared ownership. Personal ownership resolves the signed-in runner's own account, never another member's account. |
| `tools`     | Omit it to select the connection's current catalog and pin it at publication. Explicit `tools: []` selects no tools. Use an explicit list to narrow access.                                   |
| `policy`    | `allow`, `require_approval`, or `deny`. `deny` stays a denial, not an approval prompt.                                                                                                        |

The validator reports each resource as 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.

## Save drafts, go live, and accept 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. It 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.

## Use secrets and personal accounts

> **Warning**
>
> 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 stays 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 and appears in Conversations labelled **Draft**.
* It 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` keep their existing directory manifest interface.

## Invoke the live agent

These examples are generated from the same source as Studio and `agents 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
: "${SIKARU_API_KEY:?Set SIKARU_API_KEY in your environment}"
SESSION=$(sikaru execution-sessions create --project-id PROJECT_ID --harness-id support --tenant-id customer --user-id user | jq -r '.session.id')
RUN=$(sikaru execution-sessions append_turn --project-id PROJECT_ID --session-id "$SESSION" --idempotency-key "$(uuidgen)" --input '{"message":"Hello"}' | jq -r '.run.id')
sikaru runs stream_events --project-id PROJECT_ID --run-id "$RUN"
```

#### curl

```sh
: "${SIKARU_API_KEY:?Set SIKARU_API_KEY in your environment}"
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_api 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 "@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 stays 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 keep their existing review and staging/production operations; document suggestions require Go live.

## Troubleshooting

| Symptom                         | What to do                                                                                                                 |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 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).

| `session_mode`    | Behavior                                                                                    |
| ----------------- | ------------------------------------------------------------------------------------------- |
| `fresh` (default) | 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.

| Task             | Operation                                                                                                                                             |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| List schedules   | `GET /v1/projects/{project_id}/run-schedules?agent_slug=support`. Each schedule includes its next run time.                                           |
| Read occurrences | `/{schedule_id}/occurrences` for admitted and skipped times, exact version and sealed access. Use `nextBefore` as `before` to retrieve older entries. |
| Read notices     | `/{schedule_id}/notices` for actionable owner notices.                                                                                                |
| Pause or resume  | `PATCH /{schedule_id}` with `{"paused": true}` or `{"paused": false}`.                                                                                |
| Delete           | `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` keep 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 stay separate. A channel never grants personal connection authority. Publication and explicit channel activation are separate actions.

## Next steps

#### [Tools and connections](/build/tools-and-connections)

Connect the apps, MCP servers, and secrets your agent mentions.

#### [Personal channels](/build/personal-channels)

Let a personal agent act with each person's own accounts.

#### [Sessions and steering](/run/sessions-and-steering)

Continue conversations with the live agent.

#### [CLI](/sdks-and-cli/cli)

Install the CLI and explore its commands.