agent surface: create_repo through hive-c0re (#1787)

Closes the #1787 loop — the sanctioned create path now that agents
can't create repos directly. Adds:

- wire: Request::CreateRepo{repo} + Response::RepoCreated{full_name,
  clone_url} (hive-sh4re).
- agent_server: dispatch_shared arm + handle_create_repo — validates the
  repo name, then forge::create_agent_repo (org-owned repo, agent=write
  collaborator, operator-team branch protection). Returns the full name
  + clone url so the agent can git clone immediately.
- MCP: create_repo tool + CreateRepoArgs in the harness.
- a new opt-in ToolGroup::Forge (=[create_repo]) so the operator
  controls which agents can spin up repos (least privilege).

Workspace clippy -D warnings, cargo test, nix fmt all green.
This commit is contained in:
atlas 2026-06-19 12:57:01 +02:00 committed by mara
commit f1d54ce12c
4 changed files with 115 additions and 3 deletions

View file

@ -59,6 +59,11 @@ pub enum SocketReply {
hive_name: Option<String>,
swarm_name: Option<String>,
},
/// `create_repo` result — the new repo's full name + clone URL.
RepoCreated {
full_name: String,
clone_url: String,
},
}
impl From<hive_sh4re::Response> for SocketReply {
@ -100,6 +105,13 @@ impl From<hive_sh4re::Response> for SocketReply {
hive_name,
swarm_name,
},
hive_sh4re::Response::RepoCreated {
full_name,
clone_url,
} => Self::RepoCreated {
full_name,
clone_url,
},
}
}
}
@ -910,6 +922,35 @@ impl AgentServer {
.await
}
#[tool(
description = "Create a git repo through hive-c0re. You CANNOT create repos with your \
own forge token (creation is disabled) this is the only path. The repo is created in \
the c0re-owned `agents` org, you're added as a write collaborator (not owner), and the \
default branch gets branch protection so merges require an operator-team approval you \
cannot merge your own PRs. `repo` is a single name segment (letters, digits, `-`, `_`, \
`.`). Returns the new repo's full name + clone URL; clone it over \
`http://localhost:3000/agents/<repo>.git` and push/open PRs as normal."
)]
async fn create_repo(&self, Parameters(args): Parameters<CreateRepoArgs>) -> String {
let log = format!("{args:?}");
run_tool_envelope("create_repo", log, async move {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::CreateRepo { repo: args.repo })
.await;
let s = match resp {
Ok(SocketReply::RepoCreated {
full_name,
clone_url,
}) => format!("created repo {full_name} — clone: {clone_url}"),
Ok(SocketReply::Err(m)) => format!("create_repo failed: {m}"),
Ok(other) => format!("create_repo unexpected response: {other:?}"),
Err(e) => format!("create_repo transport error: {e:#}"),
};
annotate_retries(s, retries)
})
.await
}
#[tool(
description = "Schedule a reminder that lands in this agent's own inbox at a future \
time (sender will appear as `reminder`). Use for self-paced follow-ups: 'check task \
@ -1505,6 +1546,13 @@ pub struct SetStatusArgs {
pub text: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CreateRepoArgs {
/// Repo name — a single segment of letters, digits, `-`, `_`, `.`
/// (no leading `-`/`.`). The repo is created as `agents/<repo>`.
pub repo: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetAgentMetaArgs {
/// Logical name of the agent to query (e.g. `"iris"`, `"manager"`).