
kiro-cli 는 기본적으로 agent 기능을 지원한다.
/agent swap 으로 agent 간 전환도 가능하고 /tools 로 도구 사용 권한을 조정할 수 있으며 Ctrl+G 로는 sub-agent 모니터를 확인할 수 있다. 본 글은 agent 가 어떻게 동작하는지 도구와는 어떻게 묶여서 한 turn 안에서 실행되는지 작동 방식을 이해해본다.kiro 가 노출하는 agent 관련 기능들
kiro-cli chat -a 로 chat 을 띄우면 다음과 같은 화면이 뜬다.
공식 docs 에 따라 Kiro Chat을 직접 시작해 보면 다음과 같은 명령들이 나온다.
/agent,/agent swap <name>으로 현재 사용 중인 agent 를 다른 agent 로 전환한다. 기본으로 default / Plan / Guide 세 agent 가 들어 있다./tools trust <name>//tools untrust <name>//tools reset으로 도구 사용 권한을 미리 허용하거나 취소할 수 있다./guide는 Guide agent 로 직접 전환하는 단축키이며, Kiro 에 대한 안내를 제공한다./chat new는 새 conversation 을 시작한다.Ctrl+G단축키는 sub-agent 들이 동시에 돌고 있는 agents monitor 를 띄운다.
앞서 언급된 명령어들이 kiro-cli binary 안에 정적 문자열로도 존재하는 것을 볼 수 있다.
1
2
3
4
$ strings /usr/bin/kiro-cli-chat | grep -E '(default agent|Switch to Plan|Guide agent that)'
The default agent for Kiro CLI
Switch to Plan agent for breaking down ideas into implementation plans
Guide agent that answers questions about Kiro CLI features using documentation
Default 는 일반 코딩 agent, Plan 은 아이디어를 implementation plan 으로 풀어 내는 agent, Guide 는 docs 기반으로 kiro 자체에 대한 질문을 받는 agent 다.
사용자가 자기만의 agent 를 추가할 수도 있다.
/agent create 명령어를 활용하거나 (docs ) 프롬프트를 통해 Create an agent for code review 같은 자연어 요청으로 새 agent 를 생성할 수도 있다. 만들어진 agent 는 /agent swap code-review 로 전환이 가능하다.하나의 agent 가 다른 agent 를 sub-process 형태로 호출하는 것을 sub-agent 라고 한다. 하나의 agent 가 여러 child agent 를 띄워 (spawn) 일을 나누는 것이다. 바이너리에서 agent 관련된 binary strings 를 따라가 보면 agent 끼리 메세지를 주고받을 때 사용하는 별도 통신 도구가 있는데, agent 생성 시 추가되는 정의 파일을 통해 차근차근 따라가 보자.
agent 정의 파일의 위치
새 agent 를 생성하면
~/.kiro/agents/ 디렉토리에 파일이 생성된다. 최초 실행 시 해당 디렉토리에는 agent_config.json.example 한 파일만 들어 있는데, 열어 보면 agent 정의의 모든 필드의 예시를 다루고 있다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "example",
"description": "This is an example agent config (and will not be loaded unless you change it to have .json extension)",
"prompt": null,
"mcpServers": {},
"tools": [
"read", "write", "shell", "aws", "report", "introspect",
"knowledge", "thinking", "todo", "delegate", "grep", "glob",
"@mcp_server_name/mcp_tool_name",
"@mcp_server_name_without_tool_specification_to_include_all_tools"
],
"toolAliases": {},
"allowedTools": [],
"resources": [],
"hooks": {},
"toolsSettings": {},
"includeMcpJson": true,
"model": null
}
tools 배열에는 빌트인 도구 뿐 만 아니라 @mcp_server_name/mcp_tool_name 형식의 MCP 에서 제공하는 도구도 추가할 수 있다. (빌트인 도구와 외부 MCP 도구가 같은 namespace 를 공유하는 것으로 추측) 또 allowedTools / toolsSettings / hooks / toolAliases 가 별도 필드로 분리되어 있다. 권한, 설정, 후크, 별칭이 서로 다른 처리 경로를 거치는 구조다.agent 정의는 user-global (
~/.kiro/agents/) 만 있는 게 아니라 project-scoped 단계에도 생성이 가능하다. 가령 ai-dlc project root 에서는 aidlc-builder-agent 를 만들어 사용할 수 있고, 이는 user-global 이 될 필요가 없기에 project 내부에만 존재하면 되므로 project scoped 로 둔다.1
2
3
4
5
6
7
// .kiro/agents/aidlc-builder-agent.json
{
"name": "aidlc-builder-agent",
"description": "AI-DLC builder. Invoked by the orchestrator to generate questions, plans, and artifacts for a single skill.",
"prompt": "You are an AI-DLC builder agent. Read and follow the builder protocol ...",
"tools": ["read", "write", "shell"]
}
- template 의 모든 필드를 다 채울 필요는 없다. 이 child agent 는
name,description,prompt,tools4 필드만 정의한다. 부모 agent 가 11 종 이상의 도구를 노출하는 것과 달리 이 child 는 단 3 개 도구 (read,write,shell) 만 선언 한다. 이처럼 필요한 만큼만 권한을 주는 것이 하네스 엔지니어링 관점에서도 중요하다.
한 번의 프롬프트가 만드는 런타임 산출물
프롬프트 입력 시 해당 프로세스를 어떻게 처리하는지 fs trace 와 jsonl 의 변화, 호출 endpoint 를 살펴보자.
파일시스템 동작: atomic-write metadata 와 append-only jsonl
~/.kiro/sessions/cli/ 를 inotifywait 로 관찰하면서 새 대화 세션을 진행해 보면, 명확한 파일시스템 동작 패턴을 볼 수 있다.1
2
3
4
5
6
7
8
9
$ inotifywait -m -r --format '%T %e %w%f' ~/.kiro/sessions/cli/
01:05:50 CREATE <UUID>.lock
01:05:50 CREATE <UUID>.json.tmp
01:05:50 MOVED_FROM <UUID>.json.tmp
01:05:50 MOVED_TO <UUID>.json
01:05:50 CREATE <UUID>.jsonl
01:06:30 MODIFY <UUID>.jsonl
01:06:30 CREATE <UUID>.json.tmp
01:06:30 MOVED_TO <UUID>.json
세션 시작 시
.lock 으로 동시 접근을 막고, .json (turn 단위 metadata) 과 .jsonl (record 누적 log) 두 파일이 같이 만들어진다. record 추가가 일어날 때 .jsonl 은 끝에 단순 추가되고, .json 은 매번 새 .tmp 에 통째로 쓴 뒤 atomic 하게 rename 된다. record 가 추가될 때마다 metadata 가 함께 갱신되기 때문에 한 turn 에 4 ~ 8 회 정도의 파일시스템 동작이 누적된다.jsonl 의 4 종 record kind
.jsonl 한 파일이 한 chat 세션의 전체 history 다. 한 줄에 한 record 가 들어가고, 각 record 는 {version, kind, data} 구조다. record 의
kind 는 4 종류다.Prompt는 사용자가 입력한 메시지.AssistantMessage는 LLM 응답이고,thinking,text,toolUse세 종류 block 이 한 record 안에 섞여 있다.ToolResults는 도구 실행 결과.Compaction은 context window 가 차면 발생하는 자동 요약. (compact 명령어의 그것)
extended thinking 의 출력이 jsonl 에 별도 block 으로 남는다. 하나의 AssistantMessage toolUse block 을 보면, 다음과 같은 모양이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"kind": "toolUse",
"data": {
"toolUseId": "tooluse_K3Ulvs0d1xD48ZXlpHxbZe",
"name": "todo_list",
"input": {
"__tool_use_purpose": "AI-DLC 워크플로우의 전체 진행을 추적하기 위해 태스크 리스트 생성",
"command": "create",
"task_list_description": "...",
"tasks": [...]
}
}
}
도구 이름은
todo_list, 입력은 자유 형식 JSON. 각 도구 호출에는 __tool_use_purpose 라는 필드가 같이 위치한다. 이 필드는 사용자의 agent 정의에는 없고, kiro 가 spec 을 LLM 으로 보내기 직전에 빌트인 도구의 schema 에 자체적으로 추가한다. 정확한 용도를 알기 위해 __tool_use_purpose 로 binary 에서 정적 문자열을 검색해보니, 다음과 같은 내용이 나온다. 도구를 왜 사용하기로 결정했는지 그 이유를 기술하는 필드인 것 같다.1
2
__tool_use_purpose
A brief explanation why you are making this tool use.
새로 kiro-cli를 설치한 환경에서 (claude-opus-4.7 기준) 도구 사용을 필요로 하는 프롬프트 입력을 돌려 봤는데, 같은 input 에 이 필드가 비어 있는 사례도 나타났다. required 로 강제하는 필드는 아니고 필요한 경우 모델이 자율적으로 채우는 값인 것 같았다.
프롬프트 metadata 와 모델 스키마
.jsonl 옆의 .json (metadata) 파일에는 turn 단위 metadata 가 쌓인다. metadata record 한 개가 다음과 같은 필드들을 가지는 것을 볼 수 있었다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"loop_id": {
"agent_id": { "name": "kiro_default", "parent_id": null, "rand": null },
"rand": 3292184572
},
"total_request_count": 2,
"number_of_cycles": 1,
"builtin_tool_uses": 1,
"turn_duration": { "secs": 20, "nanos": 980310762 },
"end_reason": "UserTurnEnd",
"context_usage_percentage": 6.13,
"metering_usage": [
{ "value": 0.142, "unit": "credit" },
{ "value": 0.429, "unit": "credit" }
]
}
agent 의 식별자 (
agent_id) 가 (name, parent_id, rand) 세 필드로 표현된다. 한 turn 에 LLM 으로 가는 HTTP(S) 요청 횟수 (total_request_count), agent_loop 의 cycle 수 (number_of_cycles), 빌트인 도구 호출 횟수 (builtin_tool_uses), turn 의 실측 소요 시간 (turn_duration), 종료 사유 (end_reason: UserTurnEnd) 가 함께 박힌다. 비용은 token 이 아니라 metering_usage 의 credit 단위로 두 항목으로 기록된다.같은 metadata 파일 안의
rts_model_state.model_info 는 server 가 client 쪽으로 내려보낸 모델 호출 옵션 스키마를 그대로 박아 둔다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"model_id": "claude-opus-4.7",
"context_window_tokens": 1000000,
"rate_multiplier": 2.2,
"additional_fields": { "schema": { "properties": {
"thinking": {
"properties": {
"type": { "enum": ["adaptive", "disabled"] },
"display": { "enum": ["summarized", "omitted"] }
}
},
"max_tokens": { "minimum": 1024, "maximum": 128000 },
"output_config": {
"properties": { "effort": { "enum": ["low", "medium", "high", "xhigh", "max"] } }
}
}}}
}
server 가 모델 별 policy들을 마치 정적 config 서버 처럼 내려주는 방식이다.
thinking.type는 adaptive 또는 disabledthinking.display는 요약본만 보여주거나 완전히 숨김output_config.effort는 low / medium / high / xhigh / max 의 5 단계
같은 model 도 effort 단계에 따라 thinking 이 더 길어지고 응답 길이도 달라진다. opus 4.7 기준 Kiro 에서 제공하는 context window 는 1M token, rate_multiplier 는 2.2 다.
inference 호출이 향하는 endpoint와 wire format
실제 inference 호출이 어디로 향하는지는 binary strings 와 syscall trace 만으로는 추정만 가능하다.
1
2
3
4
5
6
7
8
$ strings /usr/bin/kiro-cli-chat | grep -E 'q\.[a-z-]+\.amazonaws'
https://q.{REGION}.amazonaws.com
https://q.{REGION2}.amazonaws.com/
$ strace -f -e trace=connect /usr/bin/kiro-cli-chat chat --no-interactive "..." \
| grep -oE 'sin_addr=inet_addr\("[^"]+"' | sort -u
sin_addr=inet_addr("***.***.***.***" # icn.r.cloudfront.net (Seoul edge?)
sin_addr=inet_addr(""***.***.***.***" # ec2-...compute-1.amazonaws.com
q.<region>.amazonaws.com 패턴 + AWS 의 IP 영역. 그런데 이 호출이 _실제로 어떤 요청이고 어떤 응답을 받는지_ 는 strings + strace 로는 안 보인다. 한 단계 더 들어가려면 TLS 를 풀어 본문을 읽어야 한다.mitmproxy 를 이용하면 HTTPS 트래픽을 디버깅할 수 있따. 의 root CA 를 등록하고, kiro-cli 의
HTTPS_PROXY 환경변수를 mitmproxy 로 향하게 한 뒤 한 turn 을 돌렸다. 그 결과 한 번의 입력 시 kiro 가 보낸 HTTPS 요청은 다음과 같았다.| endpoint | x-amz-target | req | resp |
|---|---|---|---|
| management.****.kiro.dev | AmazonCodeWhispererService.GetProfile | 82B | 463B |
| management.****.kiro.dev | AmazonCodeWhispererService.ListAvailableModels | 102B | 9.3KB |
| management.****.kiro.dev | AmazonCodeWhispererService.GetProfile | 82B | 463B |
| runtime.****.kiro.dev | AmazonCodeWhispererStreamingService.GenerateAssistantResponse | 39.6KB | streaming |
| q.****.amazonaws.com | AmazonCodeWhispererService.SendTelemetryEvent | 620B | 2B |
처음엔 binary strings 만 보고 inference endpoint
q.<region>.amazonaws.com 가 inference API 주소인 것으로 추측했는데 실제로는 telemetry 전용 이었다. 실제 inference 는 runtime.<region>.kiro.dev 로 향하고, profile / model list 같은 metadata 는 또 다른 도메인 management.<region>.kiro.dev 가 받는다. binary strings 에 region 이 여러 개가 있어 region 이 사용자 profile 마다 다르게 잡힌다.요청 본문은 AWS JSON 1.0 (
Content-Type: application/x-amz-json-1.0), x-amz-target 헤더로 RPC method 가 dispatch 된다. (User-Agent 포함되어 있던 aws-sdk-rust 과 lang/rust 으로 미루어 보아 rust 로 작성된 것 같다.) 그 중 GenerateAssistantResponse 의 요청 JSON 을 보게되면, 다음과 같다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"conversationState": {
"conversationId": "31b32cf1-...",
"history": [...],
"currentMessage": {
"userInputMessage": {
"content": "--- CONTEXT ENTRY BEGIN ---\nCurrent time: ...\n--- CONTEXT ENTRY END ---\n\n--- USER MESSAGE BEGIN ---\nWhat is 7 plus 11?--- USER MESSAGE END ---",
"userInputMessageContext": {
"envState": { "operatingSystem": "linux", "currentWorkingDirectory": "/home/user" },
"tools": [ ... 16 tool specs ... ]
},
"origin": "KIRO_CLI",
"modelId": "claude-opus-4.7"
}
},
"chatTriggerType": "MANUAL",
"agentTaskType": "vibe"
},
"profileArn": "arn:aws:codewhisperer:<region>:<account>:profile/<profile-id>"
}
사용자 prompt 가
--- CONTEXT ENTRY BEGIN/END ---, --- USER MESSAGE BEGIN/END --- marker 로 감싸진다. 그 위에 agentTaskType, chatTriggerType 같은 turn metadata 가 붙고, agent 의 환경 (envState) 과 도구 spec 16개가 함께 간다.응답의 Content-Type 은
application/vnd.amazon.eventstream 이다. 즉 AWS EventStream binary protocol. binary frame 의 sequence 로, 각 frame 의 :event-type 헤더가 5 종류로 나뉜다.| event-type | payload 예시 |
|---|---|
| initial-response | {"conversationId":""} |
| assistantResponseEvent | {"content":" kiro-analysis","modelId":"claude-opus-4.8"}` |
| toolUseEvent | {"input":"{\"operation","name":"fs_read","toolUseId":"tooluse_..."} |
| contextUsageEvent | {"contextUsagePercentage":2.184...} |
| meteringEvent | {"unit":"credit","unitPlural":"credits","usage":0.173...} |
assistantResponseEvent 의 content 는 단어/구 단위의 fragment 집합이다. 도구 호출이 일어나는 turn 에서는 toolUseEvent 가 여러 frame 에 걸쳐 도착하고, 각 frame의 입력이 점진적으로 누적된다. (""-> "operation" → operation... → 최종 JSON). 이 fragmentations 를 합쳐 tool_use block로 조립되는데, 아쉽게도 바이너리에서는 많은 정보를 얻을 수 없었다.1
2
$ strings /usr/bin/kiro-cli-chat | grep -E 'tool use delta'
received a tool use delta with no corresponding tool use
ListAvailableModels
ListAvailableModels 응답에는 model 별로
supportsPromptCaching, minimumTokensPerCacheCheckpoint, maximumCacheCheckpointsPerRequest 가 함께 박혀 있다. 매 요청마다 사용하는 도구 spec + 시스템 prompt 를 다시 보내지만 prompt caching 으로 누적 비용이 줄어드는 디자인이다. 이 부분이 Kiro 를 사용하면서 비용최적화를 가능케 하는 부분으로, bedrock 에서도 지원하는 기능이다. 도구를 사용할 경우
도구 호출이 필요하다고 판단될 경우 GenerateAssistantResponse 가 두 번 호출된다. 첫 호출의 응답은
toolUseEvent frame 만 존재하고, 두 번째 호출의 요청에 도구 결과가 추가되어 다시 전달된다. 그럼 응답으로 assistantResponseEvent frame 들이 최종 텍스트를 fragment 단위로 흘려 보내게 된다. 이 방식으로 count 된 것이 jsonl metadata 의 total_request_count: 2 가 가리키는 동작이다.sub-agent 호출의 jsonl 기록 구조
Ctrl+G 로 띄우는 agents orchestration monitor 기능도 놓칠 수 없다. agent 간 호출, 통신은 어떻게 기록될까. jsonl 을 보면 subagent 라는 tool 이 별도로 존재한다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"kind": "toolUse",
"data": {
"toolUseId": "tooluse_nAvc5QtMHpFsdK6zCdnPZX",
"name": "subagent",
"input": {
"__tool_use_purpose": "Run the intent-bootstrap builder to create the intent skeleton",
"mode": "blocking",
"task": "Bootstrap a fresh AI-DLC intent for a greenfield TODO web application.",
"stages": [
{
"name": "intent-bootstrap-builder",
"role": "aidlc-builder-agent",
"prompt_template": "You are the AI-DLC builder for the `aidlc-intent-bootstrap` skill ..."
}
]
}
}
}
4 필드 (
__tool_use_purpose, mode, task, stages[]) 로 input이 구성되어 있었다. mode 는 모두 blocking 이고, stages 길이는 1 또는 2 다. stage 의 role 은 agent 이름을 명시하고 있었으며 부모 agent 가 subagent 도구를 통해 N stage pipeline 을 child agent 들을 호출하는 구조였다. binary 에 PipelineStage with 5 elements + depends_on 필드가 함께 박혀 있는 것으로 보아 stage 사이의 순서도 표현 가능한 것 같았다. 한 호출이 끝나면 ToolResults 가 한 텍스트 덩어리로 돌아온다.1
2
3
4
5
Pipeline completed: 2 stages finished.
[intent-bootstrap-validator]
status: pass
validation_report_path: /home/.../validation-report.md
Summary: All 9 rules in validation-spec.md passed.
평균 길이는 2K 문자 수준이다. 거의 모든 호출이
Pipeline completed: N stages finished 로 시작한다. status 가 keyword 로 노출되는 형태라 단순한 keyword 매칭만으로도 분기가 가능하다.child 가 자기 turn 안에서 어떤 도구를 호출했는지 는 부모 jsonl 에 존재하지 않았다. 부모는
task 와 stages 만 넘기고 텍스트 값으로만 결과를 받는다. 해당 호출들 모두 일관적이라 jsonl 차원에서 강제로 분리되는 설계로 보인다. (도구 호출은 각각의 agent 가 관리하도록 하고 이는 추후 Security 확장성을 고려할 때도 복잡도를 낮게 유지할 수 있으니, 꽤나 마음에 드는 아키텍쳐였다.) 마지막으로, subagents 관련된 const limitation 을 안내하는 문자열을 발견할 수 있었다.
1
2
$ strings /usr/bin/kiro-cli-chat | grep 'spawn 4 or fewer'
You can only spawn 4 or fewer subagents at a time
앞서 언급된 대로 구현된 agents 시스템을 시각화 하면, 다음과 같다.

agent 끼리 통신하는 두 가지 방식
subagent는 (한 번의 도구 호출에서 끝나는) 분업과 같다. parent 가 task + stages 를 넘기고, 결과를 한 텍스트로 받고 종료된다. parent agent는 child agent 가 완료되어야 결과를 알려주는 blocking pipeline 이다. 그와 동등하게 session_management 라는 pipeline shape 도 확인할 수 있었다.1
2
3
4
5
6
7
8
9
10
11
$ strings /usr/bin/kiro-cli-chat | grep -E 'session_management|spawn_session: Create'
session_management
- spawn_session: Create a new persistent session with any agent config
- send_message: Send a message to another session's inbox
- read_messages: Read messages from your inbox
- list_sessions: List all active sessions
- get_session_status: Get detailed status of a session
- interrupt: Cancel a session's current work and redirect it
- inject_context: Silently add context to a session (no turn triggered)
- manage_group: Create/manage groups of sessions
- revive_session: Re-spawn a terminated worker with a new task
이게 도구가 외부에 노출하는 부분이다. 받는 쪽의 동작도 도구 description 에 그대로 적혀 있다.
1
2
3
4
5
6
7
8
$ strings /usr/bin/kiro-cli-chat | grep -A 7 'HOW RESULTS ARRIVE'
HOW RESULTS ARRIVE:
- Workers deliver results to your inbox automatically when they finish
- Your system prompt shows unread message counts → use read_messages when you see them
NOTES:
- Sessions persist and maintain full conversation history
- Messages appear in the target's system prompt as unread count
- Use use_subagent for one-off tasks; use sessions for ongoing collaboration
inbox 의 본체: symbol table 에서 자료구조 끄집어내기
외부 description 만 보면 mailbox 모델이라는 것까지는 읽힌다. 하지만 inbox 가 실제로 어떻게 구현돼 있는지는 (어떤 자료구조이고, 동시성을 어떻게 막는지, message 가 process 경계를 넘어가는지) description 만으로는 파악하기 어렵다. binary 가 stripped 가 아니라서 symbol table 에 함수 이름과 함께 내부 변수 타입을 확인할 수 있었다.
1
2
3
4
5
nm /usr/bin/kiro-cli-chat | grep -oE '5inbox10InboxStore[0-9]+[a-z_]+' | sort -u
5inbox10InboxStore12send_message
5inbox10InboxStore13read_messages
5inbox10InboxStore18get_unread_summary
5inbox10InboxStore3new
InboxStore 라는 한 struct 가 send / read / unread-summary / new 4 method 만 노출한다. 그럼 이 struct 의 내부 필드는 무엇일까. Rust 의 Drop glue 함수 이름에 generics 가 그대로 박힌다.1
2
3
4
5
6
7
$ nm /usr/bin/kiro-cli-chat | rustfilt | \
grep -E 'drop_in_place.*InboxMessage'
drop_in_place<alloc::vec::Vec<chat_cli_v2::agent::acp::orchestration::types::InboxMessage>>
drop_in_place<std::collections::hash::map::HashMap<
alloc::string::String,
alloc::vec::Vec<chat_cli_v2::agent::acp::orchestration::types::InboxMessage>
>>
InboxStore 의 타입은 HashMap<String, Vec<InboxMessage>> 다. 같은 binary 의 도구 description 에 another session's inbox / is not authorized to message session 같은 표현이 함께 박혀 있는 것으로 보아 String key 는 session id 로 추정된다. 즉, session id 마다 메시지 큐 (Vec<InboxMessage>) 를 보관하는 in-process 단일 자료구조이며 메시지가 process 경계를 넘지 않는다. (OS 단에서 Process 별로 다른 메모리 접근권한과 가상 주소를 부여하는 방식으로 메모리 공간 간섭을 최소화하기 때문에, 메모리 dump 등에서도 부가적인 이점이 있겠다.) 같은 binary 에 tokio::sync::oneshot::Sender<Result<Vec<InboxMessage>, ...>> 의 drop glue 도 함께 박혀 있다. 어디서 쓰이는지 symbol 만으로는 유추하기 어렵지만 않지만, 응답을 한 번만 보내는 oneshot 채널이 read_messages 의 응답 path 에 자연스럽게 맞을 것 같다.위 지식들을 정리해보자면 다음과 같다.

inbox.rs 의 방어로직
inbox.rs 의 line 번호 옆에 박혀 있는 메시지들을 같이 보면 이 in-process store 가 단순히 메세지를 벡터에
push 하는 것 뿐 만 아니라 safety check 로직을 수행하는 것을 볼 수 있다.1
2
3
4
5
6
7
8
9
10
$ strings /usr/bin/kiro-cli-chat | grep -E 'inbox\.rs:[0-9]+|to inbox|to self|too many|too large|max '
event crates/chat-cli-v2/src/agent/acp/orchestration/inbox.rs:83
event crates/chat-cli-v2/src/agent/acp/orchestration/inbox.rs:99
event crates/chat-cli-v2/src/agent/acp/orchestration/inbox.rs:139
Inbox full, dropped oldest message
Message delivered to inbox
Cannot send message to self
Circular messaging detected: too many messages to same target in 5 seconds
Message too large: chars (max )
Messages read from inbox
- inbox capacity 한도 (
Inbox full, dropped oldest message): inbox 의Vec가 일정 크기에 다다르면 가장 오래된 메시지를 drop 하는 ring-buffer 동작. - self-loop 차단 (
Cannot send message to self): 자기 자신의 inbox 에는 못 넣음. - circular messaging guard (
Circular messaging detected: too many messages to same target in 5 seconds): 같은 target 에 5 초 안에 너무 많은 메시지가 가면 reject. agent A → B → A 같은 무한 ping-pong 을 막는 검사다. - message size 한도 검사(
Message too large: N chars (max M)): 메시지 최대 길이 제한
PermissionStore: 권한 / rate-limit 한 계층 더
orchestration 디렉토리로 문자열 추출을 더 진행해 보면,
permissions.rs 가 별도 file 로 존재하고, 거기에 PermissionStore 라는 struct 가 new 까지 포함해 5 개 symbol 을 가지고 있다. 동작 method 는 4 가지다.1
2
3
4
5
6
$ nm /usr/bin/kiro-cli-chat | grep -oE '11permissions15PermissionStore[0-9]+[a-z_]+' | sort -u
11permissions15PermissionStore11can_message
11permissions15PermissionStore14register_child
11permissions15PermissionStore14register_group
11permissions15PermissionStore16check_rate_limit
11permissions15PermissionStore3new
can_message, register_child, register_group, check_rate_limit 의 4 가지 동작 method 와 생성자 new 다. binary strings 에 동반되는 메시지가 있다.1
2
is not authorized to message session
Rate limit exceeded: max messages per minute
즉
send_message 의 path 는 다음 순서로 묶인다.can_message(from, to): sender 가 receiver 의 inbox 에 쓸 권한이 있는가. parent → child, group 안의 peer 같은 관계가register_child/register_group으로 미리 등록돼 있어야 한다.check_rate_limit(from): 분당 최대 메시지 수를 넘었는가.- inbox.rs 의 4 자리 검사: capacity / self-loop / circular guard / size.
- HashMap 의 entry vec 에 push.
- (capacity 도달 시) oldest drop.

추가 단서가 하나 더 있다. naming module 이다.
1
2
3
nm /usr/bin/kiro-cli-chat | grep -oE 'orchestration6naming[0-9]+[a-z_]+' | sort -u
orchestration6naming16next_squire_name
orchestration6naming9next_name
next_name 은 일반 session 이름 generator, next_squire_name 은 child 전용으로 보인다. squire 어휘로 보아 parent 에 종속된 session 으로 추측된다. spawn_session 의 어느 name generator 가 어디 호출 경로에서 쓰이는지는 symbol 만으로는 파악이 어려웠다.두 통신 모델, 같은 process 안
이제 두 모델을 실제 구현 차원에서 다시 정리할 수 있다.
- subagent: 한 turn 의 도구 호출 안에서 child 들이 sequential pipeline 으로 돌고 한 텍스트로 결과가 묶여 돌아온다. parent 의
agent_loop가 child 의 turn 을 기다리는 blocking 모델. child 의 turn 은 parent jsonl 에 기록되지 않는다. - session_management: 모든 session 이 같은 process 의 한 자료구조(Inbox)를 공유하고, sender 와 receiver 로 나뉘어 일방통행으로 메세지가 흐른다. 즉시 응답이 트리거되지 않으며 session 끼리 자유롭게 호출할 수 있지만 인가받은 관계 안에서만 가능하다.
두 통신 모델 모두 process 경계를 넘지 않는 것으로 보인다. Inbox 는 같은 process 안의 HashMap 으로 관리되며 orchestration namespace 안에서 unix socket / named pipe / mmap / shared memory / tcp 같은 IPC primitive 의 symbol 은 잡히지 않았다. (
nm | grep -E 'orchestration.*(socket|pipe|mmap|ipc)'명령어로 찾아봤으나 존재하지 않았다.) subagent 도 같은 binary 의 task 로 spawn 되어 결과를 한 텍스트로 부모에게 직접 돌려주는 흐름이 위 jsonl 분석에서 확인됐다. agent 가 늘어나도 한 process 안의 자료구조 (HashMap / oneshot / Vec) 위에서 통신이 이루어지는 모델이다.마무리
/agent 슬래시 명령에서 시작해 system object, static strings 까지 살펴보면서 알게된 kiro 의 agent orchestration 은 다음과 같다.- agent 정의 파일 (
~/.kiro/agents/<name>.json) 이 한 agent 가 노출하는 도구, 권한, hook 매핑, 모델 선택을 한 file 에 묶는다. user-global 과 project-scoped 두 자리에 둘 수 있다. - session 관리 파일 (
<uuid>.json+<uuid>.jsonl) 을 통해 단일 chat 의 흐름과 metadata 를 보존한다..jsonl에 record 가 누적되고, 옆의.json에는 turn 단위 metadata 와 server 가 내려보낸 모델 호출 옵션 스키마가 매번 갱신된다. - subagents 가 한 도구 호출 안의 분업을 만든다. parent 가 task + stages 를 넘기면 child agent 들에서 실행되고, 결과는 한 텍스트로만 돌아온다. synchronous, blocking, 동시 한도 4.
- session_management 가 session 끼리의 inbox 통신을 만든다. 내부 구현은
HashMap<String, Vec<InboxMessage>>한 자료구조로, 모든 session 이 같은 process 의 같은 store 를 공유한다.send_message는 PermissionStore 의 권한 검사와 rate-limit 검사, inbox capacity / self-loop / circular guard / size 한도 5 가지 guard 를 거친 뒤 receiver 의 entry vec 에 push 한다. asynchronous, persistent, in-process.
/agent / /tools / Ctrl+G 같은 슬래시 명령은 docs 에 정리되어 있지만, 그 명령이 어떤 파일을 어떻게 갱신하는지, 한 turn 안에서 어떤 module 이 어떤 순서로 움직이는지 까지는 자세히 파악하지 않으면 잘 보이지 않는다. 보기에는 쉬워보였지만, jsonl 의 record 를 따라가 보고, 파일시스템과 syscall trace 로 하나의 응답 사이클 동작을 캡쳐하고, api wire format 까지 풀어 보고, binary strings 와 symbol table 에 박힌 line 번호와 error message 매핑시켜보면서 들었던 생각은, 정말 꼼꼼히 많은 부분을 구현해두었다는 점이다.
개인적으로 인상깊었던 디자인은 두 가지로, 첫 번째는 sub-agent 의 jsonl 분리다. child 의 turn 을 parent jsonl 에 넣지 않는 약속 덕에 실행 흔적이 한 단계 안에서 encapsulation 을 보장받는다.
두 번째는 hook 결과 캐싱이다. 본문에는 자세히 다루지 않았지만 hook 에 caching 을 둠으로써 task_executor 가 같은 입력에 대해 hook 을 반복 평가하지 않는 디자인이 컴퓨팅 리소스를 절약시키게 돕는다.
원래도 즐겨쓰던 Kiro 인데, 원리를 알고나니 더욱 더 신뢰가 느껴진다. Kiro의 발전이 기대된다.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article